From 56fb468e98d903edbe2b5b9c468c38659cfff4d6 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Tue, 11 Apr 2017 14:53:58 -0400 Subject: [PATCH 001/214] Initial commit for project-level permissions --- .../2017-03-30_Project_level_permissions.sql | 12 ++ .../NDB_Menu_Filter_candidate_list.class.inc | 12 +- .../NDB_Form_candidate_parameters.class.inc | 8 + .../php/create_timepoint.class.inc | 8 +- .../php/NDB_Form_dashboard.class.inc | 140 +++++------------- .../NDB_Menu_Filter_imaging_browser.class.inc | 12 +- .../NDB_Menu_Filter_instrument_list.class.inc | 7 + .../php/NDB_Form_new_profile.class.inc | 16 +- .../php/NDB_Menu_timepoint_list.class.inc | 8 +- .../php/NDB_Form_user_accounts.class.inc | 26 ++++ .../templates/form_edit_user.tpl | 8 + php/libraries/Project.class.inc | 90 +++++++++++ php/libraries/User.class.inc | 49 ++++++ smarty/templates/main.tpl | 5 + 14 files changed, 297 insertions(+), 104 deletions(-) create mode 100644 SQL/Archive/17.1/2017-03-30_Project_level_permissions.sql diff --git a/SQL/Archive/17.1/2017-03-30_Project_level_permissions.sql b/SQL/Archive/17.1/2017-03-30_Project_level_permissions.sql new file mode 100644 index 00000000000..bfd77e81aa8 --- /dev/null +++ b/SQL/Archive/17.1/2017-03-30_Project_level_permissions.sql @@ -0,0 +1,12 @@ +CREATE TABLE `user_project_rel` ( + `UserID` int(10) unsigned NOT NULL, + `ProjectID` int(2) NOT NULL, + PRIMARY KEY (`UserID`,`ProjectID`), + KEY `FK_user_project_rel_2` (`ProjectID`), + CONSTRAINT `FK_user_project_rel_2` FOREIGN KEY (`ProjectID`) REFERENCES `Project` (`ProjectID`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_user_project_rel_1` FOREIGN KEY (`UserID`) REFERENCES `users` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO user_project_rel (UserID, ProjectID) SELECT 1, ProjectID FROM Project; +INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, Parent, Label, OrderNumber) SELECT 'useProjectPermissions', "Enable project level permissions", 1, 0, 'boolean', ID, 'Use project-level permissions', 4 FROM ConfigSettings WHERE Name="study"; +INSERT INTO Config (ConfigID, Value) SELECT ID, "false" FROM ConfigSettings WHERE Name="useProjectPermissions"; diff --git a/modules/candidate_list/php/NDB_Menu_Filter_candidate_list.class.inc b/modules/candidate_list/php/NDB_Menu_Filter_candidate_list.class.inc index 35e91f9a459..93bec36b718 100644 --- a/modules/candidate_list/php/NDB_Menu_Filter_candidate_list.class.inc +++ b/modules/candidate_list/php/NDB_Menu_Filter_candidate_list.class.inc @@ -100,6 +100,7 @@ class NDB_Menu_Filter_Candidate_List extends NDB_Menu_Filter $this->validFilters = array( 'pso.ID', 'c.CenterID', + 'c.ProjectID', 'c.CandID', 'c.PSCID', 'c.Gender', @@ -141,6 +142,11 @@ class NDB_Menu_Filter_Candidate_List extends NDB_Menu_Filter $this->query .= " AND c.CenterID IN (" . $site_arr . ")"; } + if ($config->getSetting("useProjectPermissions")) { + $projectIds = $user->getProjectIds(); + $this->query .= " AND c.ProjectID IN (" . implode(",", $projectIds) . ")"; + } + //'COALESCE(pso.ID,1) AS Participant_Status', $this->group_by = 'c.CandID, psc.Name, c.PSCID, c.Gender'; $this->order_by = 'c.PSCID ASC'; @@ -320,7 +326,11 @@ class NDB_Menu_Filter_Candidate_List extends NDB_Menu_Filter // Project list, if applicable if ($config->getSetting("useProjects")==="true") { $list_of_projects = array(null => 'All'); - $projectList = Utility::getProjectList(); + if ($config->getSetting("useProjectPermissions")) { + $projectList = $user->getProjects(); + } else { + $projectList = Utility::getProjectList(); + } foreach ($projectList as $key => $value) { $list_of_projects[$key] =$value; } diff --git a/modules/candidate_parameters/php/NDB_Form_candidate_parameters.class.inc b/modules/candidate_parameters/php/NDB_Form_candidate_parameters.class.inc index 6baf4d1b7d2..2e2c659f699 100644 --- a/modules/candidate_parameters/php/NDB_Form_candidate_parameters.class.inc +++ b/modules/candidate_parameters/php/NDB_Form_candidate_parameters.class.inc @@ -41,6 +41,14 @@ class NDB_Form_Candidate_Parameters extends NDB_Form { //create user object $user =& User::singleton(); + $config =& NDB_Config::singleton(); + $candidate =& Candidate::singleton($this->identifier); + + if ($config->getSetting("useProjectPermissions")) { + if (!$user->hasAccessToProject($candidate->getData("ProjectID"))) { + return false; + } + } // Set global permission to control access // to different modules of candidate_parameters page diff --git a/modules/create_timepoint/php/create_timepoint.class.inc b/modules/create_timepoint/php/create_timepoint.class.inc index 94041a45d51..040d196a071 100644 --- a/modules/create_timepoint/php/create_timepoint.class.inc +++ b/modules/create_timepoint/php/create_timepoint.class.inc @@ -39,9 +39,15 @@ class Create_Timepoint extends \NDB_Form { // create user object $user =& \User::singleton(); - + $config =& \NDB_Config::singleton(); $candidate =& \Candidate::singleton($this->identifier); + if ($config->getSetting("useProjectPermissions")) { + if (!$user->hasAccessToProject($candidate->getData("ProjectID"))) { + return false; + } + } + // check user permissions return ( $user->hasPermission('data_entry') && diff --git a/modules/dashboard/php/NDB_Form_dashboard.class.inc b/modules/dashboard/php/NDB_Form_dashboard.class.inc index be9cea3b31c..2741a72523d 100644 --- a/modules/dashboard/php/NDB_Form_dashboard.class.inc +++ b/modules/dashboard/php/NDB_Form_dashboard.class.inc @@ -73,25 +73,34 @@ class NDB_Form_Dashboard extends NDB_Form } } + $useProjects = $config->getSetting('useProjects'); + $useProjectPermissions = $config->getSetting('useProjectPermissions'); + $projectIDs = $useProjectPermissions ? + $user->getProjectIds() : + array_keys(Utility::getProjectList()); + $recruitmentTarget = $config->getSetting('recruitmentTarget'); + + $overallRecruitment = Project::getRecruitment($projectIDs); + $this->createProjectProgressBar( 'overall', "Overall Recruitment", $recruitmentTarget, - $this->getTotalRecruitment() + $overallRecruitment, + $projectIDs ); - $useProjects = $config->getSetting('useProjects'); $this->tpl_data['useProjects'] = $useProjects; if ($useProjects == "true") { - $projects = Utility::getProjectList(); - foreach ($projects as $projectID => $project) { + foreach ($projectIDs as $projectID) { $projectInfo = $config->getProjectSettings($projectID); $this->createProjectProgressBar( $projectID, $projectInfo['Name'], $projectInfo['recruitmentTarget'], - $this->getTotalRecruitmentByProject($projectID) + Project::getRecruitment(array($projectID)), + $projectIDs ); } } @@ -102,8 +111,9 @@ class NDB_Form_Dashboard extends NDB_Form LEFT JOIN session s ON (s.ID=f.SessionID) LEFT JOIN candidate c ON (s.CandID=c.CandID) WHERE s.Active='Y' AND c.Active='Y' + AND c.ProjectID IN (:projectIDs) AND s.CenterID <> 1", - array() + array('projectIDs' => implode(',', $projectIDs)) ); // Tasks @@ -118,8 +128,9 @@ class NDB_Form_Dashboard extends NDB_Form LEFT JOIN candidate c ON (s.CandID=c.CandID) WHERE fqc.QCStatus IS NULL AND s.Active='Y' AND c.Active='Y' + AND c.ProjectID IN (:projectIDs) AND s.CenterID <> 1", - array() + array('projectIDs' => implode(',', $projectIDs)) ); $this->tpl_data['new_scans_site'] = 'Sites: all'; } @@ -133,8 +144,9 @@ class NDB_Form_Dashboard extends NDB_Form LEFT JOIN session s ON (flag.SessionID=s.ID) LEFT JOIN candidate c ON (s.CandID=c.CandID) WHERE s.CenterID <> 1 + AND c.ProjectID IN (:projectIDs) AND s.Active='Y' AND c.Active='Y'", - array() + array('projectIDs' => implode(',', $projectIDs)) ); $this->tpl_data['conflicts_site'] = 'Sites: all'; } else { @@ -145,8 +157,12 @@ class NDB_Form_Dashboard extends NDB_Form LEFT JOIN candidate c ON (c.CandID=s.CandID) LEFT JOIN psc ON (psc.CenterID=s.CenterID) WHERE FIND_IN_SET(psc.CenterID, :siteID) + AND c.ProjectID IN (:projectIDs) AND s.Active='Y' AND c.Active='Y'", - array('siteID' => implode(',', $siteID)) + array( + 'siteID' => implode(',', $siteID), + 'projectIDs' => implode(',', $projectIDs) + ) ); $this->tpl_data['conflicts_site'] = 'Sites: All User Sites'; } @@ -159,8 +175,9 @@ class NDB_Form_Dashboard extends NDB_Form LEFT JOIN session s ON (s.ID=flag.SessionID) LEFT JOIN candidate c ON (s.CandID=c.CandID) WHERE flag.Data_entry='In Progress' + AND c.ProjectID IN (:projectIDs) AND s.Active='Y' AND c.Active='Y' AND s.CenterID <> 1", - array() + array('projectIDs' => implode(',', $projectIDs)) ); $this->tpl_data['incomplete_forms_site'] = 'Sites: all'; } else { @@ -171,8 +188,12 @@ class NDB_Form_Dashboard extends NDB_Form LEFT JOIN psc ON (psc.CenterID=s.CenterID) WHERE Data_entry='In Progress' AND FIND_IN_SET(psc.CenterID, :siteID) + AND c.ProjectID IN (:projectIDs) AND s.Active='Y' AND c.Active='Y'", - array('siteID' => implode(',', $siteID)) + array( + 'siteID' => implode(',', $siteID), + 'projectIDs' => implode(',', $projectIDs) + ) ); $this->tpl_data['incomplete_forms_site'] = $site; } @@ -187,8 +208,9 @@ class NDB_Form_Dashboard extends NDB_Form LEFT JOIN session s ON (s.ID=fg.SessionID) LEFT JOIN candidate c ON (c.CandID=s.CandID) WHERE Review_Done IS NULL + AND c.ProjectID IN (:projectIDs) AND c.Active='Y' AND s.Active='Y'", - array() + array('projectIDs' => implode(',', $projectIDs)) ); $this->tpl_data['radiology_review_site'] = 'Sites: all'; } @@ -222,10 +244,11 @@ class NDB_Form_Dashboard extends NDB_Form $this->tpl_data['violated_scans'] = $DB->pselectOne( "SELECT COUNT(*) FROM mri_protocol_violated_scans LEFT JOIN candidate c USING (CandID) - WHERE COALESCE(c.CenterID, 0) <> 1", + WHERE COALESCE(c.CenterID, 0) <> 1 + AND c.ProjectID IN (:projectIDs)", /* include null CenterIDs so we don't accidentally filter things out */ - array() + array('projectIDs' => implode(',', $projectIDs)) ); $this->tpl_data['violated_scans_site'] = 'Sites: all'; } @@ -308,87 +331,6 @@ class NDB_Form_Dashboard extends NDB_Form } } - /** - * Gets the total count of candidates associated with a specific project - * - * @return int - */ - function getTotalRecruitment() - { - $DB = Database::singleton(); - $totalRecruitment = $DB->pselectOne( - "SELECT COUNT(*) FROM candidate c - WHERE c.Active='Y' AND c.Entity_type='Human' AND c.CenterID <> 1", - array() - ); - return $totalRecruitment; - } - - /** - * Gets the total count of candidates associated with a specific project - * - * @param int $projectID Project ID - * - * @return int - */ - function getTotalRecruitmentByProject($projectID) - { - $DB = Database::singleton(); - $totalRecruitment = $DB->pselectOne( - "SELECT COUNT(*) - FROM candidate c - WHERE c.Active='Y' AND c.ProjectID=:PID AND c.Entity_type='Human' - AND c.CenterID <> 1", - array('PID' => $projectID) - ); - return $totalRecruitment; - } - - /** - * Gets the total count of candidates of a specific gender - * - * @param string $gender gender (male or female) - * - * @return int - */ - function getTotalGender($gender) - { - $DB = Database::singleton(); - $total = $DB->pselectOne( - "SELECT COUNT(c.CandID) - FROM candidate c - WHERE c.Gender=:Gender AND c.Active='Y' AND c.Entity_type='Human' - AND c.CenterID <> 1", - array('Gender' => $gender) - ); - return $total; - } - - /** - * Gets the total count of candidates of a specific gender, - * associated with a specific project - * - * @param string $gender gender (male or female) - * @param int $projectID Project ID - * - * @return int - */ - function getTotalGenderByProject($gender, $projectID) - { - $DB = Database::singleton(); - $total = $DB->pselectOne( - "SELECT COUNT(c.CandID) - FROM candidate c - WHERE c.Gender=:Gender AND c.Active='Y' AND c.ProjectID=:PID - AND c.Entity_type='Human' AND c.CenterID <> 1", - array( - 'Gender' => $gender, - 'PID' => $projectID, - ) - ); - return $total; - } - /** * Creates the template data for a progress bar * @@ -411,18 +353,18 @@ class NDB_Form_Dashboard extends NDB_Form = $recruitmentTarget; if ($ID == 'overall') { - $totalFemales = $this->getTotalGender("Female"); + $totalFemales = Project::getTotalFemales(); } else { - $totalFemales = $this->getTotalGenderByProject("Female", $ID); + $totalFemales = Project::getTotalFemales(array($ID)); } $this->tpl_data['recruitment'][$ID]['female_total'] = $totalFemales; $this->tpl_data['recruitment'][$ID]['female_percent'] = round($totalFemales / $recruitmentTarget * 100); if ($ID == 'overall') { - $totalMales = $this->getTotalGender("Male"); + $totalMales = Project::getTotalMales(); } else { - $totalMales = $this->getTotalGenderByProject("Male", $ID); + $totalMales = Project::getTotalMales(array($ID)); } $this->tpl_data['recruitment'][$ID]['male_total'] = $totalMales; $this->tpl_data['recruitment'][$ID]['male_percent'] diff --git a/modules/imaging_browser/php/NDB_Menu_Filter_imaging_browser.class.inc b/modules/imaging_browser/php/NDB_Menu_Filter_imaging_browser.class.inc index 20fc3daa7bb..4832dac9bfc 100644 --- a/modules/imaging_browser/php/NDB_Menu_Filter_imaging_browser.class.inc +++ b/modules/imaging_browser/php/NDB_Menu_Filter_imaging_browser.class.inc @@ -122,6 +122,11 @@ class NDB_Menu_Filter_Imaging_Browser extends NDB_Menu_Filter $config =& NDB_Config::singleton(); $user =& User::singleton(); $DB = Database::singleton(); + + if ($config->getSetting("useProjectPermissions")) { + $this->query .= " AND c.ProjectID IN (" . implode(",", $user->getProjectIds()) . ")"; + } + if (!$user->hasPermission('imaging_browser_view_allsites')) { $site_arr = implode(",", $user->getCenterIDs()); $this->query .= " AND c.CenterID IN (" . $site_arr . ")"; @@ -269,7 +274,12 @@ class NDB_Menu_Filter_Imaging_Browser extends NDB_Menu_Filter $config =& NDB_Config::singleton(); if ($config->getSetting('useProjects') === "true") { $list_of_projects = $allAr; - $projectList = Utility::getProjectList(); + if ($config->getSetting("useProjectPermissions")) { + $projectList = $user->getProjects(); + } else { + $projectList = Utility::getProjectList(); + } + foreach ($projectList as $key => $value) { $list_of_projects[$key] =$value; } diff --git a/modules/instrument_list/php/NDB_Menu_Filter_instrument_list.class.inc b/modules/instrument_list/php/NDB_Menu_Filter_instrument_list.class.inc index fa35bc1b949..1060b4d46ed 100644 --- a/modules/instrument_list/php/NDB_Menu_Filter_instrument_list.class.inc +++ b/modules/instrument_list/php/NDB_Menu_Filter_instrument_list.class.inc @@ -38,12 +38,19 @@ class NDB_Menu_Filter_Instrument_List extends NDB_Menu_Filter { // create user object $user =& User::singleton(); + $config =& NDB_Config::singleton(); $timePoint =& TimePoint::singleton($_REQUEST['sessionID']); $candID = $timePoint->getCandID(); $candidate =& Candidate::singleton($candID); + if ($config->getSetting("useProjectPermissions")) { + if (!$user->hasAccessToProject($candidate->getData("ProjectID"))) { + return false; + } + } + // check user permissions return ($user->hasPermission('access_all_profiles') || (in_array( diff --git a/modules/new_profile/php/NDB_Form_new_profile.class.inc b/modules/new_profile/php/NDB_Form_new_profile.class.inc index cb84565d08f..5ea6d34bb69 100644 --- a/modules/new_profile/php/NDB_Form_new_profile.class.inc +++ b/modules/new_profile/php/NDB_Form_new_profile.class.inc @@ -115,6 +115,7 @@ class NDB_Form_New_Profile extends NDB_Form function new_profile()//@codingStandardsIgnoreLine { $config =& NDB_Config::singleton(); + $user =& User::singleton(); $startYear = $config->getSetting('startYear'); $endYear = $config->getSetting('endYear'); $ageMax = $config->getSetting('ageMax'); @@ -185,7 +186,11 @@ class NDB_Form_New_Profile extends NDB_Form } if ($config->getSetting("useProjects") == "true") { - $projects = Utility::getProjectList(); + if ($config->getSetting("useProjectPermissions")) { + $projects = $user->getProjects(); + } else { + $projects = Utility::getProjectList(); + } $projList = array('' => ''); foreach ($projects as $projectID => $projectName) { $projList[$projectID] = $projectName; @@ -298,6 +303,15 @@ class NDB_Form_New_Profile extends NDB_Form $errors['ProjectID'] = "Project is required"; } + $useProjectsPermissions = $config->getSetting('useProjectPermissions'); + if ($useProjectsPermissions === "true" && !empty($values['ProjectID'])) { + $user =& User::singleton(); + + if (!$user->hasAccessToProject($values['ProjectID'])) { + $errors['ProjectID'] = "User does not belong to this project"; + } + } + return $errors; } diff --git a/modules/timepoint_list/php/NDB_Menu_timepoint_list.class.inc b/modules/timepoint_list/php/NDB_Menu_timepoint_list.class.inc index ffef8edf499..a986af5bd40 100644 --- a/modules/timepoint_list/php/NDB_Menu_timepoint_list.class.inc +++ b/modules/timepoint_list/php/NDB_Menu_timepoint_list.class.inc @@ -33,9 +33,15 @@ class NDB_Menu_Timepoint_List extends NDB_Menu { // create user object $user =& User::singleton(); - + $config =& NDB_Config::singleton(); $candidate =& Candidate::singleton($_REQUEST['candID']); + if ($config->getSetting("useProjectPermissions")) { + if (!$user->hasAccessToProject($candidate->getData("ProjectID"))) { + return false; + } + } + // check user permissions if ($user->hasPermission('access_all_profiles') || (in_array( diff --git a/modules/user_accounts/php/NDB_Form_user_accounts.class.inc b/modules/user_accounts/php/NDB_Form_user_accounts.class.inc index 20174d28d4f..10a133a6b4a 100644 --- a/modules/user_accounts/php/NDB_Form_user_accounts.class.inc +++ b/modules/user_accounts/php/NDB_Form_user_accounts.class.inc @@ -242,6 +242,24 @@ class NDB_Form_User_Accounts extends NDB_Form unset($values['CenterIDs']); // END multi-site UPDATE } + + // multi-project UPDATE + $us_curr_projects = $values['ProjectIDs']; + if (!$this->isCreatingNewUser()) { + $DB->delete('user_project_rel', array("UserID" => $uid)); + foreach ($us_curr_projects as $project) { + $DB->insert( + 'user_project_rel', + array( + "UserID" => $uid, + "ProjectID" => $project, + ) + ); + } + } + unset($values['ProjectIDs']); + // END multi-project UPDATE + // EXAMINER UPDATE $ex_curr_sites =array(); $ex_prev_sites =array(); @@ -598,6 +616,14 @@ class NDB_Form_User_Accounts extends NDB_Form array('multiple' => 'multiple') ); + $projectOptions = Utility::getProjectList(); + $this->addSelect( + 'ProjectIDs', + 'Projects', + $projectOptions, + array('multiple' => 'multiple') + ); + if ($editor->hasPermission('examiner_multisite')) { //get site aliases $DB =& Database::singleton(); diff --git a/modules/user_accounts/templates/form_edit_user.tpl b/modules/user_accounts/templates/form_edit_user.tpl index 3333b8a74f3..085163f969d 100644 --- a/modules/user_accounts/templates/form_edit_user.tpl +++ b/modules/user_accounts/templates/form_edit_user.tpl @@ -289,6 +289,14 @@ $(document).ready(function() { {/if} {/if} +
+ +
+ {$form.ProjectIDs.html} +
+
- -
{$form.Email.html}
{$form.Instrument.html}
diff --git a/php/libraries/JSONInstrumentToLINSTConverter.class.inc b/php/libraries/JSONInstrumentToLINSTConverter.class.inc index 83ac7792023..d1df9fcad3a 100644 --- a/php/libraries/JSONInstrumentToLINSTConverter.class.inc +++ b/php/libraries/JSONInstrumentToLINSTConverter.class.inc @@ -39,7 +39,7 @@ class JSONInstrumentToLINSTConverter } $linstLines = array_merge( - self::generateStandardLines($obj["Meta"]["ShortName"], $obj["Meta"]["LongName"], $year), + self::generateStandardLines($obj["Meta"]["ShortName"], $obj["Meta"]["LongName"][$lang], $year, $lang), self::convertElements($obj["Elements"], $lang) ); @@ -55,12 +55,14 @@ class JSONInstrumentToLINSTConverter * * @return array Array of standard LINST lines */ - static function generateStandardLines($table, $title, $year) + static function generateStandardLines($table, $title, $year, $lang = 'en-ca') { + $dateLabel = $lang === 'fr-ca' ? "Date d'administration" : "Date of Administration"; + $lines = array(); $lines[] = "table{@}{$table}"; $lines[] = "title{@}{$title}"; - $lines[] = "date{@}Date_taken{@}Date of Administration{@}2006{@}{$year}"; + $lines[] = "date{@}Date_taken{@}{$dateLabel}{@}2006{@}{$year}"; $lines[] = "static{@}Candidate_Age{@}Candidate Age (Months)"; $lines[] = "static{@}Window_Difference{@}Window Difference (+/- Days)"; $lines[] = "select{@}Examiner{@}Examiner{@}NULL=>''"; diff --git a/php/libraries/NDB_BVL_Instrument.class.inc b/php/libraries/NDB_BVL_Instrument.class.inc index abd7caa2d79..8f99b6c0ac5 100644 --- a/php/libraries/NDB_BVL_Instrument.class.inc +++ b/php/libraries/NDB_BVL_Instrument.class.inc @@ -434,6 +434,7 @@ class NDB_BVL_Instrument extends NDB_Page $formArray['tableID'] = "instrument"; } + $formArray['lang'] = $_REQUEST["lang"] ? $_REQUEST["lang"] : "en-ca"; $smarty->assign('form', $formArray); $html = $smarty->fetch("directentry_form.tpl"); } elseif (isset($_REQUEST['json']) == 'true') { diff --git a/smarty/templates/directentry.tpl b/smarty/templates/directentry.tpl index 30776cdc076..504c610ba09 100644 --- a/smarty/templates/directentry.tpl +++ b/smarty/templates/directentry.tpl @@ -36,7 +36,7 @@ {if $finalpage || $complete} {elseif $pageNum && $totalPages} - Page {$pageNum} of {$totalPages} + Page {$pageNum} / {$totalPages} {/if} @@ -133,12 +133,20 @@ Submit data {else} {/if} diff --git a/smarty/templates/directentry_form.tpl b/smarty/templates/directentry_form.tpl index ea292b9bf98..daa802f7c3c 100644 --- a/smarty/templates/directentry_form.tpl +++ b/smarty/templates/directentry_form.tpl @@ -5,10 +5,24 @@ {$element.html} {elseif $element.type eq hidden} {$element.html} + {elseif $element.name eq Examiner} + + + + + + + {elseif $element.name eq Candidate_Age OR $element.name eq Window_Difference} {elseif $element.name eq lorisSubHeader} {$element.label} + {elseif $element.type eq static AND $element.name neq Date_taken AND $element.name neq Candidate_Age AND $element.name neq Window_Difference} + + + {$element.html} + + {elseif $element.type eq static AND $element.error} @@ -17,6 +31,21 @@ {$element.error} + {elseif $element.name eq Date_taken AND $form.lang eq "fr-ca"} + + + {if $element.required} + * + {/if} Date d'administration + + + {if $element.error} + {$element.error} +
+ {/if} + {$element.html} + + {else} @@ -29,9 +58,7 @@ {$element.error}
{/if} - {if $element.type neq static} - {$element.html} - {/if} + {$element.html} {/if} diff --git a/smarty/templates/main.tpl b/smarty/templates/main.tpl index b2ddd986fc8..f74d0e3a634 100644 --- a/smarty/templates/main.tpl +++ b/smarty/templates/main.tpl @@ -179,15 +179,6 @@  {if count($user.Projects) === 1}Project{else} Projects{/if}: {join(', ', array_values($user.Projects))}

-
\n \n );\n }\n});\n\n/**\n * Textbox Component\n * React wrapper for a element.\n */\nvar TextboxElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Date Component\n * React wrapper for a element.\n */\nvar DateElement = React.createClass({\n\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Numeric Component\n * React wrapper for a element.\n */\nvar NumericElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n min: React.PropTypes.number.isRequired,\n max: React.PropTypes.number.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n min: null,\n max: null,\n label: '',\n value: '',\n id: null,\n required: false,\n disabled: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * File Component\n * React wrapper for a simple or 'multiple' element.\n\t */\n\tvar SelectElement = React.createClass({\n\t displayName: 'SelectElement',\n\t\n\t\n\t propTypes: {\n\t name: React.PropTypes.string.isRequired,\n\t options: React.PropTypes.oneOfType([React.PropTypes.array, React.PropTypes.object]),\n\t label: React.PropTypes.string,\n\t value: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array]),\n\t id: React.PropTypes.string,\n\t class: React.PropTypes.string,\n\t multiple: React.PropTypes.bool,\n\t disabled: React.PropTypes.bool,\n\t required: React.PropTypes.bool,\n\t emptyOption: React.PropTypes.bool,\n\t hasError: React.PropTypes.bool,\n\t errorMessage: React.PropTypes.string,\n\t onUserInput: React.PropTypes.func\n\t },\n\t\n\t getDefaultProps: function getDefaultProps() {\n\t return {\n\t name: '',\n\t options: {},\n\t label: '',\n\t value: undefined,\n\t id: '',\n\t class: '',\n\t multiple: false,\n\t disabled: false,\n\t required: false,\n\t emptyOption: true,\n\t hasError: false,\n\t errorMessage: 'The field is required!',\n\t onUserInput: function onUserInput() {\n\t console.warn('onUserInput() callback is not set');\n\t }\n\t };\n\t },\n\t\n\t handleChange: function handleChange(e) {\n\t var value = e.target.value;\n\t var options = e.target.options;\n\t\n\t // Multiple values\n\t if (this.props.multiple && options.length > 1) {\n\t value = [];\n\t for (var i = 0, l = options.length; i < l; i++) {\n\t if (options[i].selected) {\n\t value.push(options[i].value);\n\t }\n\t }\n\t }\n\t\n\t this.props.onUserInput(this.props.name, value);\n\t },\n\t render: function render() {\n\t var multiple = this.props.multiple ? 'multiple' : null;\n\t var required = this.props.required ? 'required' : null;\n\t var disabled = this.props.disabled ? 'disabled' : null;\n\t var options = this.props.options;\n\t var errorMessage = null;\n\t var emptyOptionHTML = null;\n\t var requiredHTML = null;\n\t var elementClass = 'row form-group';\n\t\n\t // Add required asterix\n\t if (required) {\n\t requiredHTML = React.createElement(\n\t 'span',\n\t { className: 'text-danger' },\n\t '*'\n\t );\n\t }\n\t\n\t // Add empty option\n\t if (this.props.emptyOption) {\n\t emptyOptionHTML = React.createElement('option', null);\n\t }\n\t\n\t // Add error message\n\t if (this.props.hasError || this.props.required && this.props.value === \"\") {\n\t errorMessage = React.createElement(\n\t 'span',\n\t null,\n\t this.props.errorMessage\n\t );\n\t elementClass = 'row form-group has-error';\n\t }\n\t\n\t // Default to empty string for regular select and to empty array for 'multiple' select\n\t var value = this.props.value || (multiple ? [] : \"\");\n\t\n\t return React.createElement(\n\t 'div',\n\t { className: elementClass },\n\t React.createElement(\n\t 'label',\n\t { className: 'col-sm-3 control-label', htmlFor: this.props.label },\n\t this.props.label,\n\t requiredHTML\n\t ),\n\t React.createElement(\n\t 'div',\n\t { className: 'col-sm-9' },\n\t React.createElement(\n\t 'select',\n\t {\n\t name: this.props.name,\n\t multiple: multiple,\n\t className: 'form-control',\n\t id: this.props.label,\n\t value: value,\n\t onChange: this.handleChange,\n\t required: required,\n\t disabled: disabled\n\t },\n\t emptyOptionHTML,\n\t Object.keys(options).map(function (option) {\n\t return React.createElement(\n\t 'option',\n\t { value: option, key: option },\n\t options[option]\n\t );\n\t })\n\t ),\n\t errorMessage\n\t )\n\t );\n\t }\n\t});\n\t\n\t/**\n\t * Textarea Component\n\t * React wrapper for a \n \n \n );\n }\n});\n\n/**\n * Textbox Component\n * React wrapper for a element.\n */\nvar TextboxElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Date Component\n * React wrapper for a element.\n */\nvar DateElement = React.createClass({\n\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Numeric Component\n * React wrapper for a element.\n */\nvar NumericElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n min: React.PropTypes.number.isRequired,\n max: React.PropTypes.number.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n min: null,\n max: null,\n label: '',\n value: '',\n id: null,\n required: false,\n disabled: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * File Component\n * React wrapper for a simple or 'multiple' element.\n\t */\n\tvar SelectElement = React.createClass({\n\t displayName: 'SelectElement',\n\t\n\t\n\t propTypes: {\n\t name: React.PropTypes.string.isRequired,\n\t options: React.PropTypes.oneOfType([React.PropTypes.array, React.PropTypes.object]),\n\t label: React.PropTypes.string,\n\t value: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array]),\n\t id: React.PropTypes.string,\n\t class: React.PropTypes.string,\n\t multiple: React.PropTypes.bool,\n\t disabled: React.PropTypes.bool,\n\t required: React.PropTypes.bool,\n\t emptyOption: React.PropTypes.bool,\n\t hasError: React.PropTypes.bool,\n\t errorMessage: React.PropTypes.string,\n\t onUserInput: React.PropTypes.func\n\t },\n\t\n\t getDefaultProps: function getDefaultProps() {\n\t return {\n\t name: '',\n\t options: {},\n\t label: '',\n\t value: undefined,\n\t id: '',\n\t class: '',\n\t multiple: false,\n\t disabled: false,\n\t required: false,\n\t emptyOption: true,\n\t hasError: false,\n\t errorMessage: 'The field is required!',\n\t onUserInput: function onUserInput() {\n\t console.warn('onUserInput() callback is not set');\n\t }\n\t };\n\t },\n\t\n\t handleChange: function handleChange(e) {\n\t var value = e.target.value;\n\t var options = e.target.options;\n\t\n\t // Multiple values\n\t if (this.props.multiple && options.length > 1) {\n\t value = [];\n\t for (var i = 0, l = options.length; i < l; i++) {\n\t if (options[i].selected) {\n\t value.push(options[i].value);\n\t }\n\t }\n\t }\n\t\n\t this.props.onUserInput(this.props.name, value);\n\t },\n\t render: function render() {\n\t var multiple = this.props.multiple ? 'multiple' : null;\n\t var required = this.props.required ? 'required' : null;\n\t var disabled = this.props.disabled ? 'disabled' : null;\n\t var options = this.props.options;\n\t var errorMessage = null;\n\t var emptyOptionHTML = null;\n\t var requiredHTML = null;\n\t var elementClass = 'row form-group';\n\t\n\t // Add required asterix\n\t if (required) {\n\t requiredHTML = React.createElement(\n\t 'span',\n\t { className: 'text-danger' },\n\t '*'\n\t );\n\t }\n\t\n\t // Add empty option\n\t if (this.props.emptyOption) {\n\t emptyOptionHTML = React.createElement('option', null);\n\t }\n\t\n\t // Add error message\n\t if (this.props.hasError || this.props.required && this.props.value === \"\") {\n\t errorMessage = React.createElement(\n\t 'span',\n\t null,\n\t this.props.errorMessage\n\t );\n\t elementClass = 'row form-group has-error';\n\t }\n\t\n\t // Default to empty string for regular select and to empty array for 'multiple' select\n\t var value = this.props.value || (multiple ? [] : \"\");\n\t\n\t return React.createElement(\n\t 'div',\n\t { className: elementClass },\n\t React.createElement(\n\t 'label',\n\t { className: 'col-sm-3 control-label', htmlFor: this.props.label },\n\t this.props.label,\n\t requiredHTML\n\t ),\n\t React.createElement(\n\t 'div',\n\t { className: 'col-sm-9' },\n\t React.createElement(\n\t 'select',\n\t {\n\t name: this.props.name,\n\t multiple: multiple,\n\t className: 'form-control',\n\t id: this.props.label,\n\t value: value,\n\t onChange: this.handleChange,\n\t required: required,\n\t disabled: disabled\n\t },\n\t emptyOptionHTML,\n\t Object.keys(options).map(function (option) {\n\t return React.createElement(\n\t 'option',\n\t { value: option, key: option },\n\t options[option]\n\t );\n\t })\n\t ),\n\t errorMessage\n\t )\n\t );\n\t }\n\t});\n\t\n\t/**\n\t * Textarea Component\n\t * React wrapper for a \n \n \n );\n }\n});\n\n/**\n * Textbox Component\n * React wrapper for a element.\n */\nvar TextboxElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Date Component\n * React wrapper for a element.\n */\nvar DateElement = React.createClass({\n\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Numeric Component\n * React wrapper for a element.\n */\nvar NumericElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n min: React.PropTypes.number.isRequired,\n max: React.PropTypes.number.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n min: null,\n max: null,\n label: '',\n value: '',\n id: null,\n required: false,\n disabled: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * File Component\n * React wrapper for a simple or 'multiple' element.\n\t */\n\tvar SelectElement = React.createClass({\n\t displayName: 'SelectElement',\n\t\n\t\n\t propTypes: {\n\t name: React.PropTypes.string.isRequired,\n\t options: React.PropTypes.oneOfType([React.PropTypes.array, React.PropTypes.object]),\n\t label: React.PropTypes.string,\n\t value: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array]),\n\t id: React.PropTypes.string,\n\t class: React.PropTypes.string,\n\t multiple: React.PropTypes.bool,\n\t disabled: React.PropTypes.bool,\n\t required: React.PropTypes.bool,\n\t emptyOption: React.PropTypes.bool,\n\t hasError: React.PropTypes.bool,\n\t errorMessage: React.PropTypes.string,\n\t onUserInput: React.PropTypes.func\n\t },\n\t\n\t getDefaultProps: function getDefaultProps() {\n\t return {\n\t name: '',\n\t options: {},\n\t label: '',\n\t value: undefined,\n\t id: '',\n\t class: '',\n\t multiple: false,\n\t disabled: false,\n\t required: false,\n\t emptyOption: true,\n\t hasError: false,\n\t errorMessage: 'The field is required!',\n\t onUserInput: function onUserInput() {\n\t console.warn('onUserInput() callback is not set');\n\t }\n\t };\n\t },\n\t\n\t handleChange: function handleChange(e) {\n\t var value = e.target.value;\n\t var options = e.target.options;\n\t\n\t // Multiple values\n\t if (this.props.multiple && options.length > 1) {\n\t value = [];\n\t for (var i = 0, l = options.length; i < l; i++) {\n\t if (options[i].selected) {\n\t value.push(options[i].value);\n\t }\n\t }\n\t }\n\t\n\t this.props.onUserInput(this.props.name, value);\n\t },\n\t render: function render() {\n\t var multiple = this.props.multiple ? 'multiple' : null;\n\t var required = this.props.required ? 'required' : null;\n\t var disabled = this.props.disabled ? 'disabled' : null;\n\t var options = this.props.options;\n\t var errorMessage = null;\n\t var emptyOptionHTML = null;\n\t var requiredHTML = null;\n\t var elementClass = 'row form-group';\n\t\n\t // Add required asterix\n\t if (required) {\n\t requiredHTML = React.createElement(\n\t 'span',\n\t { className: 'text-danger' },\n\t '*'\n\t );\n\t }\n\t\n\t // Add empty option\n\t if (this.props.emptyOption) {\n\t emptyOptionHTML = React.createElement('option', null);\n\t }\n\t\n\t // Add error message\n\t if (this.props.hasError || this.props.required && this.props.value === \"\") {\n\t errorMessage = React.createElement(\n\t 'span',\n\t null,\n\t this.props.errorMessage\n\t );\n\t elementClass = 'row form-group has-error';\n\t }\n\t\n\t // Default to empty string for regular select and to empty array for 'multiple' select\n\t var value = this.props.value || (multiple ? [] : \"\");\n\t\n\t return React.createElement(\n\t 'div',\n\t { className: elementClass },\n\t React.createElement(\n\t 'label',\n\t { className: 'col-sm-3 control-label', htmlFor: this.props.label },\n\t this.props.label,\n\t requiredHTML\n\t ),\n\t React.createElement(\n\t 'div',\n\t { className: 'col-sm-9' },\n\t React.createElement(\n\t 'select',\n\t {\n\t name: this.props.name,\n\t multiple: multiple,\n\t className: 'form-control',\n\t id: this.props.label,\n\t value: value,\n\t onChange: this.handleChange,\n\t required: required,\n\t disabled: disabled\n\t },\n\t emptyOptionHTML,\n\t Object.keys(options).map(function (option) {\n\t return React.createElement(\n\t 'option',\n\t { value: option, key: option },\n\t options[option]\n\t );\n\t })\n\t ),\n\t errorMessage\n\t )\n\t );\n\t }\n\t});\n\t\n\t/**\n\t * Textarea Component\n\t * React wrapper for a \n \n \n );\n }\n});\n\n/**\n * Textbox Component\n * React wrapper for a element.\n */\nvar TextboxElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Date Component\n * React wrapper for a element.\n */\nvar DateElement = React.createClass({\n\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Numeric Component\n * React wrapper for a element.\n */\nvar NumericElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n min: React.PropTypes.number.isRequired,\n max: React.PropTypes.number.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n min: null,\n max: null,\n label: '',\n value: '',\n id: null,\n required: false,\n disabled: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * File Component\n * React wrapper for a simple or 'multiple' element.\n\t */\n\tvar SelectElement = React.createClass({\n\t displayName: 'SelectElement',\n\t\n\t\n\t propTypes: {\n\t name: React.PropTypes.string.isRequired,\n\t options: React.PropTypes.oneOfType([React.PropTypes.array, React.PropTypes.object]),\n\t label: React.PropTypes.string,\n\t value: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array]),\n\t id: React.PropTypes.string,\n\t class: React.PropTypes.string,\n\t multiple: React.PropTypes.bool,\n\t disabled: React.PropTypes.bool,\n\t required: React.PropTypes.bool,\n\t emptyOption: React.PropTypes.bool,\n\t hasError: React.PropTypes.bool,\n\t errorMessage: React.PropTypes.string,\n\t onUserInput: React.PropTypes.func\n\t },\n\t\n\t getDefaultProps: function getDefaultProps() {\n\t return {\n\t name: '',\n\t options: {},\n\t label: '',\n\t value: undefined,\n\t id: '',\n\t class: '',\n\t multiple: false,\n\t disabled: false,\n\t required: false,\n\t emptyOption: true,\n\t hasError: false,\n\t errorMessage: 'The field is required!',\n\t onUserInput: function onUserInput() {\n\t console.warn('onUserInput() callback is not set');\n\t }\n\t };\n\t },\n\t\n\t handleChange: function handleChange(e) {\n\t var value = e.target.value;\n\t var options = e.target.options;\n\t\n\t // Multiple values\n\t if (this.props.multiple && options.length > 1) {\n\t value = [];\n\t for (var i = 0, l = options.length; i < l; i++) {\n\t if (options[i].selected) {\n\t value.push(options[i].value);\n\t }\n\t }\n\t }\n\t\n\t this.props.onUserInput(this.props.name, value);\n\t },\n\t render: function render() {\n\t var multiple = this.props.multiple ? 'multiple' : null;\n\t var required = this.props.required ? 'required' : null;\n\t var disabled = this.props.disabled ? 'disabled' : null;\n\t var options = this.props.options;\n\t var errorMessage = null;\n\t var emptyOptionHTML = null;\n\t var requiredHTML = null;\n\t var elementClass = 'row form-group';\n\t\n\t // Add required asterix\n\t if (required) {\n\t requiredHTML = React.createElement(\n\t 'span',\n\t { className: 'text-danger' },\n\t '*'\n\t );\n\t }\n\t\n\t // Add empty option\n\t if (this.props.emptyOption) {\n\t emptyOptionHTML = React.createElement('option', null);\n\t }\n\t\n\t // Add error message\n\t if (this.props.hasError || this.props.required && this.props.value === \"\") {\n\t errorMessage = React.createElement(\n\t 'span',\n\t null,\n\t this.props.errorMessage\n\t );\n\t elementClass = 'row form-group has-error';\n\t }\n\t\n\t // Default to empty string for regular select and to empty array for 'multiple' select\n\t var value = this.props.value || (multiple ? [] : \"\");\n\t\n\t return React.createElement(\n\t 'div',\n\t { className: elementClass },\n\t React.createElement(\n\t 'label',\n\t { className: 'col-sm-3 control-label', htmlFor: this.props.label },\n\t this.props.label,\n\t requiredHTML\n\t ),\n\t React.createElement(\n\t 'div',\n\t { className: 'col-sm-9' },\n\t React.createElement(\n\t 'select',\n\t {\n\t name: this.props.name,\n\t multiple: multiple,\n\t className: 'form-control',\n\t id: this.props.label,\n\t value: value,\n\t onChange: this.handleChange,\n\t required: required,\n\t disabled: disabled\n\t },\n\t emptyOptionHTML,\n\t Object.keys(options).map(function (option) {\n\t return React.createElement(\n\t 'option',\n\t { value: option, key: option },\n\t options[option]\n\t );\n\t })\n\t ),\n\t errorMessage\n\t )\n\t );\n\t }\n\t});\n\t\n\t/**\n\t * Textarea Component\n\t * React wrapper for a \n \n \n );\n }\n});\n\n/**\n * Textbox Component\n * React wrapper for a element.\n */\nvar TextboxElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Date Component\n * React wrapper for a element.\n */\nvar DateElement = React.createClass({\n\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Numeric Component\n * React wrapper for a element.\n */\nvar NumericElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n min: React.PropTypes.number.isRequired,\n max: React.PropTypes.number.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n min: null,\n max: null,\n label: '',\n value: '',\n id: null,\n required: false,\n disabled: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * File Component\n * React wrapper for a simple or 'multiple' element.\n\t */\n\tvar SelectElement = React.createClass({\n\t displayName: 'SelectElement',\n\t\n\t\n\t propTypes: {\n\t name: React.PropTypes.string.isRequired,\n\t options: React.PropTypes.oneOfType([React.PropTypes.array, React.PropTypes.object]),\n\t label: React.PropTypes.string,\n\t value: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array]),\n\t id: React.PropTypes.string,\n\t class: React.PropTypes.string,\n\t multiple: React.PropTypes.bool,\n\t disabled: React.PropTypes.bool,\n\t required: React.PropTypes.bool,\n\t emptyOption: React.PropTypes.bool,\n\t hasError: React.PropTypes.bool,\n\t errorMessage: React.PropTypes.string,\n\t onUserInput: React.PropTypes.func\n\t },\n\t\n\t getDefaultProps: function getDefaultProps() {\n\t return {\n\t name: '',\n\t options: {},\n\t label: '',\n\t value: undefined,\n\t id: '',\n\t class: '',\n\t multiple: false,\n\t disabled: false,\n\t required: false,\n\t emptyOption: true,\n\t hasError: false,\n\t errorMessage: 'The field is required!',\n\t onUserInput: function onUserInput() {\n\t console.warn('onUserInput() callback is not set');\n\t }\n\t };\n\t },\n\t\n\t handleChange: function handleChange(e) {\n\t var value = e.target.value;\n\t var options = e.target.options;\n\t\n\t // Multiple values\n\t if (this.props.multiple && options.length > 1) {\n\t value = [];\n\t for (var i = 0, l = options.length; i < l; i++) {\n\t if (options[i].selected) {\n\t value.push(options[i].value);\n\t }\n\t }\n\t }\n\t\n\t this.props.onUserInput(this.props.name, value);\n\t },\n\t render: function render() {\n\t var multiple = this.props.multiple ? 'multiple' : null;\n\t var required = this.props.required ? 'required' : null;\n\t var disabled = this.props.disabled ? 'disabled' : null;\n\t var options = this.props.options;\n\t var errorMessage = null;\n\t var emptyOptionHTML = null;\n\t var requiredHTML = null;\n\t var elementClass = 'row form-group';\n\t\n\t // Add required asterix\n\t if (required) {\n\t requiredHTML = React.createElement(\n\t 'span',\n\t { className: 'text-danger' },\n\t '*'\n\t );\n\t }\n\t\n\t // Add empty option\n\t if (this.props.emptyOption) {\n\t emptyOptionHTML = React.createElement('option', null);\n\t }\n\t\n\t // Add error message\n\t if (this.props.hasError || this.props.required && this.props.value === \"\") {\n\t errorMessage = React.createElement(\n\t 'span',\n\t null,\n\t this.props.errorMessage\n\t );\n\t elementClass = 'row form-group has-error';\n\t }\n\t\n\t // Default to empty string for regular select and to empty array for 'multiple' select\n\t var value = this.props.value || (multiple ? [] : \"\");\n\t\n\t return React.createElement(\n\t 'div',\n\t { className: elementClass },\n\t React.createElement(\n\t 'label',\n\t { className: 'col-sm-3 control-label', htmlFor: this.props.label },\n\t this.props.label,\n\t requiredHTML\n\t ),\n\t React.createElement(\n\t 'div',\n\t { className: 'col-sm-9' },\n\t React.createElement(\n\t 'select',\n\t {\n\t name: this.props.name,\n\t multiple: multiple,\n\t className: 'form-control',\n\t id: this.props.label,\n\t value: value,\n\t onChange: this.handleChange,\n\t required: required,\n\t disabled: disabled\n\t },\n\t emptyOptionHTML,\n\t Object.keys(options).map(function (option) {\n\t return React.createElement(\n\t 'option',\n\t { value: option, key: option },\n\t options[option]\n\t );\n\t })\n\t ),\n\t errorMessage\n\t )\n\t );\n\t }\n\t});\n\t\n\t/**\n\t * Textarea Component\n\t * React wrapper for a \n \n \n );\n }\n});\n\n/**\n * Textbox Component\n * React wrapper for a element.\n */\nvar TextboxElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Date Component\n * React wrapper for a element.\n */\nvar DateElement = React.createClass({\n\n propTypes: {\n name: React.PropTypes.string.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n\n getDefaultProps: function() {\n return {\n name: '',\n label: '',\n value: '',\n id: null,\n disabled: false,\n required: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n // Add required asterix\n if (required) {\n requiredHTML = *;\n }\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * Numeric Component\n * React wrapper for a element.\n */\nvar NumericElement = React.createClass({\n propTypes: {\n name: React.PropTypes.string.isRequired,\n min: React.PropTypes.number.isRequired,\n max: React.PropTypes.number.isRequired,\n label: React.PropTypes.string,\n value: React.PropTypes.string,\n id: React.PropTypes.string,\n disabled: React.PropTypes.bool,\n required: React.PropTypes.bool,\n onUserInput: React.PropTypes.func\n },\n getDefaultProps: function() {\n return {\n name: '',\n min: null,\n max: null,\n label: '',\n value: '',\n id: null,\n required: false,\n disabled: false,\n onUserInput: function() {\n console.warn('onUserInput() callback is not set');\n }\n };\n },\n handleChange: function(e) {\n this.props.onUserInput(this.props.name, e.target.value);\n },\n render: function() {\n var disabled = this.props.disabled ? 'disabled' : null;\n var required = this.props.required ? 'required' : null;\n var requiredHTML = null;\n\n return (\n
\n \n
\n \n
\n
\n );\n }\n});\n\n/**\n * File Component\n * React wrapper for a simple or 'multiple' { this.updateSelectedInstrument(e.target.value)}} - > - { - this.names.map((name) => { - return ( - - ); - }) - } - - -
- -
- -
- - - -
- { console.log('save!') }} - /> -
- - ); - } -} - -window.onload = function() { - const instrumentsEl = document.querySelector('#instruments'); - const instruments = JSON.parse(instrumentsEl.dataset.json).reduce((result, instrument) => { - result[instrument.Meta.ShortName] = instrument; - return result; - }, {}); - - ReactDOM.render(, document.getElementById("container")); -}; diff --git a/htdocs/survey.php b/htdocs/survey.php index 5ea1539d110..8232efa3155 100644 --- a/htdocs/survey.php +++ b/htdocs/survey.php @@ -406,7 +406,6 @@ function display() $this->updateStatus('In Progress'); $this->tpl_data['workspace'] = $workspace; } - $this->tpl_data['lang'] = $_REQUEST['lang'] ? $_REQUEST['lang'] : 'en-ca'; $smarty = new Smarty_neurodb; $smarty->assign($this->tpl_data); $smarty->display('directentry.tpl'); diff --git a/modules/dashboard/php/NDB_Form_dashboard.class.inc b/modules/dashboard/php/NDB_Form_dashboard.class.inc index ac9e86cb59b..5471c3b7388 100644 --- a/modules/dashboard/php/NDB_Form_dashboard.class.inc +++ b/modules/dashboard/php/NDB_Form_dashboard.class.inc @@ -114,10 +114,10 @@ class NDB_Form_Dashboard extends NDB_Form "SELECT COUNT(DISTINCT s.ID) FROM files f LEFT JOIN session s ON (s.ID=f.SessionID) - LEFT JOIN candidate c ON (s.CandID=c.CandID) - WHERE fqc.QCStatus IS NULL - AND s.Active='Y' AND c.Active='Y' - AND s.CenterID <> 1", + LEFT JOIN candidate c ON (s.CandID=c.CandID) + WHERE (MRIQCStatus='' OR MRIQCPending='Y') + AND s.Active='Y' AND c.Active='Y' + AND s.CenterID <> 1", array() ); $this->tpl_data['new_scans_site'] = 'Sites: all'; diff --git a/modules/new_profile/php/NDB_Form_new_profile.class.inc b/modules/new_profile/php/NDB_Form_new_profile.class.inc index a1f07911fee..09c0f400f36 100644 --- a/modules/new_profile/php/NDB_Form_new_profile.class.inc +++ b/modules/new_profile/php/NDB_Form_new_profile.class.inc @@ -115,18 +115,21 @@ class NDB_Form_New_Profile extends NDB_Form */ function new_profile()//@codingStandardsIgnoreLine { - $config =& NDB_Config::singleton(); - $startYear = $config->getSetting('startYear'); - $endYear = $config->getSetting('endYear'); - $ageMax = $config->getSetting('ageMax'); - $ageMin = $config->getSetting('ageMin'); - $dateOptions = array( - 'language' => 'en', - 'format' => 'YMd', - 'addEmptyOption' => true, - 'minYear' => $startYear - $ageMax, - 'maxYear' => $endYear - $ageMin, - ); + $config =& NDB_Config::singleton(); + $startYear = $config->getSetting('startYear'); + $endYear = $config->getSetting('endYear'); + $ageMax = $config->getSetting('ageMax'); + $ageMin = $config->getSetting('ageMin'); + $dobFormat = $config->getSetting('dobFormat'); + + $dateOptions = array( + 'language' => 'en', + 'format' => $dobFormat, + 'addEmptyOption' => true, + 'minYear' => $startYear - $ageMax, + 'maxYear' => $endYear - $ageMin, + ); + $dateAttributes = ['class' => 'form-control input-sm input-date']; // add date of birth diff --git a/modules/survey_accounts/jsx/columnFormatter.js b/modules/survey_accounts/jsx/columnFormatter.js index 2ddcd5e4b54..6576f019bbe 100644 --- a/modules/survey_accounts/jsx/columnFormatter.js +++ b/modules/survey_accounts/jsx/columnFormatter.js @@ -15,7 +15,7 @@ function formatColumn(column, cell, rowData, rowHeaders) { row[header] = rowData[index]; }, this); if (column === 'URL') { - var url = loris.BaseURL + "/survey-react.php?key=" + row.URL; + var url = loris.BaseURL + "/survey.php?key=" + row.URL; return (
{cell}); } return {cell}; diff --git a/modules/survey_accounts/php/NDB_Form_survey_accounts.class.inc b/modules/survey_accounts/php/NDB_Form_survey_accounts.class.inc index fcc44307603..8de085b8a5f 100644 --- a/modules/survey_accounts/php/NDB_Form_survey_accounts.class.inc +++ b/modules/survey_accounts/php/NDB_Form_survey_accounts.class.inc @@ -219,7 +219,7 @@ class NDB_Form_Survey_Accounts extends NDB_Form $msg_data = array( 'study' => $config->getSetting("title"), - 'url' => $www['url'] . '/survey-react.php?key=' . + 'url' => $www['url'] . '/survey.php?key=' . urlencode($key), 'EmailForm' => $values['email_dialog'], ); diff --git a/modules/survey_accounts/php/NDB_Menu_Filter_survey_accounts.class.inc b/modules/survey_accounts/php/NDB_Menu_Filter_survey_accounts.class.inc index 2eedf644996..0f298656679 100644 --- a/modules/survey_accounts/php/NDB_Menu_Filter_survey_accounts.class.inc +++ b/modules/survey_accounts/php/NDB_Menu_Filter_survey_accounts.class.inc @@ -55,7 +55,7 @@ class NDB_Menu_Filter_Survey_Accounts extends NDB_Menu_Filter $this->columns = array( 'c.PSCID AS PSCID', 's.Visit_label AS Visit', - //'p.Email as Email', + 'p.Email as Email', 'p.Test_name as SurveyName', 'p.OneTimePassword as URL', 'p.Status', @@ -65,7 +65,7 @@ class NDB_Menu_Filter_Survey_Accounts extends NDB_Menu_Filter $this->validFilters = array( 'c.PSCID', 's.Visit_label', - //'p.Email', + 'p.Email', 'p.Test_name', 'p.OneTimePassword', 'p.Status', @@ -74,7 +74,7 @@ class NDB_Menu_Filter_Survey_Accounts extends NDB_Menu_Filter $this->formToFilter = array( 'PSCID' => 'c.PSCID', 'VisitLabel' => 's.Visit_label', - //'Email' => 'p.Email', + 'Email' => 'p.Email', 'key' => 'p.OneTimePassword', 'Instrument' => 'p.Test_name', ); diff --git a/modules/survey_accounts/templates/menu_survey_accounts.tpl b/modules/survey_accounts/templates/menu_survey_accounts.tpl index d53dc574d7b..b1da0604f23 100644 --- a/modules/survey_accounts/templates/menu_survey_accounts.tpl +++ b/modules/survey_accounts/templates/menu_survey_accounts.tpl @@ -21,6 +21,8 @@
+ +
{$form.Email.html}
{$form.Instrument.html}
diff --git a/modules/user_accounts/php/NDB_Form_user_accounts.class.inc b/modules/user_accounts/php/NDB_Form_user_accounts.class.inc index 545d6ae6c92..b73b352ced5 100644 --- a/modules/user_accounts/php/NDB_Form_user_accounts.class.inc +++ b/modules/user_accounts/php/NDB_Form_user_accounts.class.inc @@ -267,38 +267,16 @@ class NDB_Form_User_Accounts extends NDB_Form } unset($values['CenterIDs']); // END multi-site UPDATE - } - // EXAMINER UPDATE - $ex_curr_sites =array(); - $ex_prev_sites =array(); - - //get sites where user is already an examiner at for compare - $prev_sites = $DB->pselect( - " - SELECT centerID - FROM examiners - WHERE full_name=:fn", - array( - "fn" => $values['Real_name'], - ) - ); - foreach ($prev_sites as $row=>$center) { - array_push($ex_prev_sites, $center['centerID']); - } - foreach ($values as $k=>$v) { - //examiner fields - if (preg_match("/^ex_[0-9]+$/", $k)) { - //get centerID - $parse_key =explode('_', $k); - $cid = $parse_key[1]; - array_push($ex_curr_sites, $cid); - - //Check if examiner already in db for site - $result = $DB->pselectRow( - " - SELECT full_name, centerID - FROM examiners - WHERE full_name=:fn AND centerID=:cid", + + // EXAMINER UPDATE + // first check if an update is needed then actually do it + if (!empty($values['examiner_radiologist']) + && !empty($values['examiner_pending']) + ) { + $examinerID = $DB->pselect( + "SELECT e.examinerID + FROM examiners e + WHERE e.full_name=:fn", array( "fn" => $values['Real_name'], ) diff --git a/modules/user_accounts/templates/form_edit_user.tpl b/modules/user_accounts/templates/form_edit_user.tpl index 5dc50c7b017..d6a787c4378 100644 --- a/modules/user_accounts/templates/form_edit_user.tpl +++ b/modules/user_accounts/templates/form_edit_user.tpl @@ -289,13 +289,22 @@ {/if}
{/if} + {if $form.errors.sites_group} +
+ {else}
- -
- {$form.CenterIDs.html} -
+ {/if} + +
+ {$form.CenterIDs.html} +
+ {if $form.errors.sites_group} +
+ {$form.errors.sites_group} +
+ {/if}
{if $form.errors.examiner_sites}
diff --git a/php/libraries/GenerateTableSQLFromLINST.class.inc b/php/libraries/GenerateTableSQLFromLINST.class.inc deleted file mode 100644 index ec8ad378b22..00000000000 --- a/php/libraries/GenerateTableSQLFromLINST.class.inc +++ /dev/null @@ -1,123 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -/** - * Generate SQL from LINST String - * - * @category Main - * @package Main - * @author Loris Team - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ - - -class GenerateTableSQLFromLINST -{ - /** - * Generate SQL from LINST String - * - * @param string $linstString linst string - * - * @return string SQL table schema - */ - static function generate($linstString) - { - $items = explode("\n", trim($linstString)); - - foreach ($items AS $item) { - $bits = explode("{@}", trim($item)); - if (preg_match("/Examiner[0-9]*/", (string)(array_key_exists(1, $bits) ? $bits[1] : null))) { - continue; - } - switch($bits[0]){ - //generate the CREATE TABLE syntax - case "table": - $output = ""; - if (isset($opts["D"])) { - $output = "DROP TABLE IF EXISTS `$bits[1]`;\n"; - } - $output .= "CREATE TABLE `$bits[1]` (\n"; - $output .= "`CommentID` varchar(255) NOT NULL default '',\n - `UserID` varchar(255) default NULL,\n - `Examiner` varchar(255) default NULL,\n - `Testdate` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n - `Data_entry_completion_status` enum('Incomplete','Complete') NOT NULL default 'Incomplete',\n"; - break; - - //no SQL need be generated. - case "title": - case "header": - continue; - break; - - //generate specific column definitions for specific types of HTML elements - default: - if ((array_key_exists(1, $bits) ? $bits[1] : "") == "") { - continue; - } - if ($bits[0]=="select") { - $bits[0] =self::_enumizeOptions( - array_key_exists(3, $bits) ? $bits[3] : null, - $table = array(), - $bits[1] - ); - } else if ((array_key_exists(0, $bits) ? $bits[0] : null) =="selectmultiple") { - $bits[0] ="varchar(255)"; - } else if ((array_key_exists(0, $bits) ? $bits[0] : null) == "textarea") { - $bits[0] ="text"; - } else if ((array_key_exists(0, $bits) ? $bits[0] : null) == "text") { - $bits[0] ="varchar(255)"; - } else if ((array_key_exists(0, $bits) ? $bits[0] : null) == "checkbox") { - $bits[0] ="varchar(255)"; - } else if ((array_key_exists(0, $bits) ? $bits[0] : null) == "static") { - $bits[0] ="varchar(255)"; - } else if ((array_key_exists(0, $bits) ? $bits[0] : null) == "radio") { - $bits[0] =enumizeOptions($bits[3], $table = array(), $bits[1]); - } - if (array_key_exists(2, $bits)) { - $bits[2] =htmlspecialchars($bits[2]); - } - $output .="`$bits[1]` $bits[0] default NULL,\n"; - } - - } - $output .="PRIMARY KEY (`CommentID`)\n - );\n"; - return $output; - } - - /** - * Generate SQL enum from LINST options - * - * @param string $options LINST options string - * @param string $table table - * @param string $name name - * - * @return string SQL enum string - */ - static function _enumizeOptions($options, $table, $name) - { - $options =explode("{-}", $options); - foreach ($options as $option) { - $option =explode("=>", $option); - if ($option[0]!='NULL') { - $enum[] =$option[0]; - } - } - if (!is_array($enum)) { - echo "$table $name $options\n"; - } - $enum =implode(",", $enum); - return "enum($enum)"; - } -} \ No newline at end of file diff --git a/php/libraries/JSONInstrumentToLINSTConverter.class.inc b/php/libraries/JSONInstrumentToLINSTConverter.class.inc deleted file mode 100644 index d1df9fcad3a..00000000000 --- a/php/libraries/JSONInstrumentToLINSTConverter.class.inc +++ /dev/null @@ -1,144 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -/** - * Convert JSON Instruments to LINST instruments - * - * @category Main - * @package Main - * @author Loris Team - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ - -class JSONInstrumentToLINSTConverter -{ - /** - * Converts JSON instrument to LINST instrument - * - * @param string $json instrument in JSON format - * @param string $lang language code - * @param string $year year - * - * @return string LINST String - */ - static function convert($json, $lang, $year = null) - { - $obj = json_decode($json, true); - if (!$year) { - $year = (new \DateTime)->format("Y"); - } - - $linstLines = array_merge( - self::generateStandardLines($obj["Meta"]["ShortName"], $obj["Meta"]["LongName"][$lang], $year, $lang), - self::convertElements($obj["Elements"], $lang) - ); - - return implode("\n", $linstLines); - } - - /** - * Generates standard LINST lines - * - * @param string $table table name - * @param string $title title - * @param string $year year - * - * @return array Array of standard LINST lines - */ - static function generateStandardLines($table, $title, $year, $lang = 'en-ca') - { - $dateLabel = $lang === 'fr-ca' ? "Date d'administration" : "Date of Administration"; - - $lines = array(); - $lines[] = "table{@}{$table}"; - $lines[] = "title{@}{$title}"; - $lines[] = "date{@}Date_taken{@}{$dateLabel}{@}2006{@}{$year}"; - $lines[] = "static{@}Candidate_Age{@}Candidate Age (Months)"; - $lines[] = "static{@}Window_Difference{@}Window Difference (+/- Days)"; - $lines[] = "select{@}Examiner{@}Examiner{@}NULL=>''"; - return $lines; - } - - /** - * Convert JSON instrument elements into LINST lines - * - * @param array $elements JSON elements - * @param string $lang language key - * - * @return array Array of LINST lines - */ - static function convertElements($elements, $lang) - { - return array_map( - function ($element) use ($lang) { - switch ($element["Type"]) { - case "label": - return self::convertLabelElement($element, $lang); - break; - case "select": - return self::convertSelectElement($element, $lang); - break; - } - }, - $elements - ); - } - - /** - * Convert JSON label element to LINST line - * - * @param array $element Label element - * @param string $lang language key - * - * @return string LINST line - */ - static function convertLabelElement($element, $lang) - { - if (!array_key_exists($lang, $element['Description'])) return ""; - return "static{@}{@}{$element['Description'][$lang]}"; - } - - /** - * Convert JSON select element to LINST line - * - * @param array $element Select element - * @param string $lang language key - * - * @return string LINST line - */ - static function convertSelectElement($element, $lang) - { - $options = self::convertSelectElementOptions($element['Options']['Values'][$lang]); - $selectType = $element['Options']['AllowMultiple'] === true ? 'multiselect' : 'select'; - return "{$selectType}{@}{$element['Name']}{@}{$element['Description'][$lang]}{@}{$options}"; - } - - /** - * Convert JSON select options to LINST option format - * - * @param array $options JSON Select element options - * - * @return string LINST format options - */ - static function convertSelectElementOptions($options) - { - $result = array("NULL=>''"); - foreach ($options as $index => $value) { - $result[] = "'{$index}'=>'$value'"; - } - - //$result[] = "'not_answered'=>'Not Answered'"; - - return implode("{-}", $result); - } -} \ No newline at end of file diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 6c922e0a32a..7f601720fdf 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -692,7 +692,8 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument array("multiple" => "multiple") ); } else { - $this->addSelect( + $this->form->addElement( + 'select', $pieces[1], $pieces[2], $opt diff --git a/php/libraries/User.class.inc b/php/libraries/User.class.inc index 2638687fbba..ae5506f60b7 100644 --- a/php/libraries/User.class.inc +++ b/php/libraries/User.class.inc @@ -298,6 +298,24 @@ class User extends UserPermissions return $site_list; } + /** + * Returns all user's sites that are StudySites + * + * @return array + */ + function getStudySites() + { + $site_arr = $this->userInfo['CenterIDs']; + $user_study_sites = array(); + foreach ($site_arr as $key => $val) { + $site[$key] = &Site::singleton($val); + if ($site[$key]->isStudySite()) { + $user_study_sites[$val] = $site[$key]->getCenterName(); + } + } + return $user_study_sites; + } + /** * Checks that the user's email is valid * @@ -458,5 +476,37 @@ class User extends UserPermissions $this->update($updateArray); } + + /** + * Determines if the user has a center + * + * @param int $center_id The center id + * + * @return bool + */ + public function hasCenter($center_id) + { + return in_array( + $center_id, + $this->getCenterIDs() + ); + } + /** + * Determines if the user has a permission + * for a center + * + * @param string $code The permission code + * @param int $center_id The center id + * + * @return bool + */ + public function hasCenterPermission($code, $center_id) + { + if ($this->hasPermission("superuser")) { + return true; + } + return $this->hasPermission($code) + && $this->hasCenter($center_id); + } } ?> diff --git a/smarty/templates/directentry.tpl b/smarty/templates/directentry.tpl index 7329d94b579..f50ac0c6c71 100644 --- a/smarty/templates/directentry.tpl +++ b/smarty/templates/directentry.tpl @@ -7,7 +7,6 @@ {$study_title} - @@ -32,15 +31,10 @@ - - Date: {$smarty.now|date_format:"%B %e %Y"} - -
+ Date: {$smarty.now|date_format:"%B %e %Y"} {if $finalpage || $complete} {elseif $pageNum && $totalPages} - - Page {$pageNum} / {$totalPages} - + Page {$pageNum} of {$totalPages} {/if} @@ -105,7 +99,7 @@ - @@ -125,31 +119,23 @@ Submit data {if $prevpage} {if $prevpage eq 'top'} - + {else} - + {/if} {/if} {if $nextpage} - {else} {/if} diff --git a/smarty/templates/directentry_form.tpl b/smarty/templates/directentry_form.tpl index daa802f7c3c..5ccb346116b 100644 --- a/smarty/templates/directentry_form.tpl +++ b/smarty/templates/directentry_form.tpl @@ -5,24 +5,10 @@ {$element.html} {elseif $element.type eq hidden} {$element.html} - {elseif $element.name eq Examiner} - - - - - - - {elseif $element.name eq Candidate_Age OR $element.name eq Window_Difference} {elseif $element.name eq lorisSubHeader} {$element.label} - {elseif $element.type eq static AND $element.name neq Date_taken AND $element.name neq Candidate_Age AND $element.name neq Window_Difference} - - - {$element.html} - - {elseif $element.type eq static AND $element.error} @@ -31,21 +17,6 @@ {$element.error} - {elseif $element.name eq Date_taken AND $form.lang eq "fr-ca"} - - - {if $element.required} - * - {/if} Date d'administration - - - {if $element.error} - {$element.error} -
- {/if} - {$element.html} - - {else} @@ -58,10 +29,10 @@ {$element.error}
{/if} - {$element.html} + {$element.html} {/if} {/foreach} - \ No newline at end of file + diff --git a/smarty/templates/instrument-preview.tpl b/smarty/templates/instrument-preview.tpl deleted file mode 100644 index 3487de05c6f..00000000000 --- a/smarty/templates/instrument-preview.tpl +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - {$study_title} - - - - - - - - -
-
- - diff --git a/smarty/templates/main.tpl b/smarty/templates/main.tpl index 27a3c1ac5af..72c8dca2127 100644 --- a/smarty/templates/main.tpl +++ b/smarty/templates/main.tpl @@ -143,8 +143,12 @@ diff --git a/test/unittests/JSONInstrumentToLINSTConverter_Test.php b/test/unittests/JSONInstrumentToLINSTConverter_Test.php deleted file mode 100644 index 6e12b76343f..00000000000 --- a/test/unittests/JSONInstrumentToLINSTConverter_Test.php +++ /dev/null @@ -1,177 +0,0 @@ -''", - "static{@}{@}Label1", - ]); - - $json = ' - { - "Meta": { - "InstrumentVersion": "v0.0.1", - "InstrumentFormatVersion": "v0.0.3-dev", - "ShortName": "wasi", - "LongName": { - "en-ca": "WASIIII" - }, - "IncludeMetaDataFields": true - }, - "Elements": [ - { - "Type": "label", - "Description": { - "en-ca": "Label1" - }, - "Options": {} - } - ] - } - '; - - $this->assertEquals( - $EXPECTED_RESULT, - JSONInstrumentToLINSTConverter::convert($json, 'en-ca', $year) - ); - } - /** - * @test - * @covers JSONInstrumentToLINSTConverter::generateStandardLines - */ - function generateStandardLines() { - $table = 'wasi'; - $title = 'Wechsler Abbreviated Scale of Intelligence'; - $year = '2017'; - - $EXPECTED_RESULT = []; - $EXPECTED_RESULT[] = "table{@}{$table}"; - $EXPECTED_RESULT[] = "title{@}{$title}"; - $EXPECTED_RESULT[] = "date{@}Date_taken{@}Date of Administration{@}2006{@}{$year}"; - $EXPECTED_RESULT[] = "static{@}Candidate_Age{@}Candidate Age (Months)"; - $EXPECTED_RESULT[] = "static{@}Window_Difference{@}Window Difference (+/- Days)"; - $EXPECTED_RESULT[] = "select{@}Examiner{@}Examiner{@}NULL=>''"; - - $this->assertEquals( - $EXPECTED_RESULT, - JSONInstrumentToLINSTConverter::generateStandardLines($table, $title, $year) - ); - } - - /** - * @test - * @covers JSONInstrumentToLINSTConverter::convertLabelElement - */ - function convertLabelElement() { - $labelElement = array( - "Type" => "label", - "Description" => array( - "en-ca" => "English Label", - "fr-ca" => "Label français", - ), - "Options" => array() - ); - - $this->assertEquals( - "static{@}{@}English Label", - JSONInstrumentToLINSTConverter::convertLabelElement($labelElement, 'en-ca') - ); - } - - /** - * @test - * @covers JSONInstrumentToLINSTConverter::convertSelectElement - */ - function convertSelectElementSingle() { - $selectElement = array( - "Type" => "label", - "Name" => "s1", - "Description" => array( - "en-ca" => "English Label", - "fr-ca" => "Label français", - ), - "Options" => array( - "Values" => array( - "en-ca" => array( - "1" => "Option 1", - "2" => "Option 2", - ), - "fr-ca" => array( - "1" => "Option 1", - "2" => "Option 2" - ) - ), - "AllowMultiple" => false, - "RequireResponse" => false - ) - ); - - $this->assertEquals( - "select{@}s1{@}Label français{@}NULL=>''{-}'1'=>'Option 1'{-}'2'=>'Option 2'", - JSONInstrumentToLINSTConverter::convertSelectElement($selectElement, 'fr-ca') - ); - } - - /** - * @test - * @covers JSONInstrumentToLINSTConverter::convertSelectElement - */ - function convertSelectElementMultiple() { - $selectElement = array( - "Type" => "label", - "Name" => "s1", - "Description" => array( - "en-ca" => "English Label", - "fr-ca" => "Label français", - ), - "Options" => array( - "Values" => array( - "en-ca" => array( - "1" => "Option 1", - "2" => "Option 2", - ), - "fr-ca" => array( - "1" => "Option 1", - "2" => "Option 2" - ) - ), - "AllowMultiple" => true, - "RequireResponse" => false - ) - ); - - $this->assertEquals( - "multiselect{@}s1{@}Label français{@}NULL=>''{-}'1'=>'Option 1'{-}'2'=>'Option 2'", - JSONInstrumentToLINSTConverter::convertSelectElement($selectElement, 'fr-ca') - ); - } - - /** - * @test - * @covers JSONInstrumentToLINSTConverter::convertSelectElementOptions - */ - function convertSelectElementOptions() { - $options = array( - "1" => "Option 1", - "2" => "Option 2", - ); - - $this->assertEquals( - "NULL=>''{-}'1'=>'Option 1'{-}'2'=>'Option 2'", - JSONInstrumentToLINSTConverter::convertSelectElementOptions($options) - ); - } -} -?> From a7c37bc1ecee02677c4fece2f58cb40dc1099c50 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 7 Sep 2017 12:42:30 -0400 Subject: [PATCH 179/214] Revert webpack changes --- webpack.config.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index dc4ac37182e..8b52e99f443 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,7 +1,7 @@ var webpack = require('webpack'); var path = require('path'); -var config = [{ +var config = { entry: { './htdocs/js/components/DynamicDataTable.js': './jsx/DynamicDataTable.js', './htdocs/js/components/PaginationLinks.js': './jsx/PaginationLinks.js', @@ -49,7 +49,6 @@ var config = [{ './modules/data_integrity_flag/js/index.js': './modules/data_integrity_flag/jsx/index.js', './modules/imaging_uploader/js/index.js': './modules/imaging_uploader/jsx/index.js', './htdocs/js/modules/direct-entry-react.compiled.js': './htdocs/js/modules/direct-entry-react.js', - './htdocs/js/modules/instrument-preview.compiled.js': './htdocs/js/modules/instrument-preview.js', './htdocs/js/modules/instrument-view.compiled.js': './htdocs/js/modules/instrument-view.js', }, output: { @@ -91,11 +90,6 @@ var config = [{ }, devtool: 'source-map', plugins: [new webpack.optimize.UglifyJsPlugin({mangle: false})] -}]; - -var fs = require('fs'); -if (fs.existsSync('./project/webpack.config.js')) { - config.push(require('./project/webpack.config')); -} +}; module.exports = config; From 86debcf797de4d0f92573b550ff3c53de56affdf Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 7 Sep 2017 13:35:29 -0400 Subject: [PATCH 180/214] Revert more unrelated changes --- .travis.yml | 1 - composer.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 25b0e4d17a7..098faa55e9d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,6 @@ before_install: # Ensure node version is up to date - nvm install node - mkdir -p project project/libraries - - mkdir -p project project/modules - phpenv config-rm xdebug.ini install: diff --git a/composer.json b/composer.json index 566f28c5854..6b5b854112c 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,6 @@ "pre-install-cmd": "mkdir -p project/libraries" }, "autoload" : { - "classmap": ["project/modules", "project/libraries", "php/libraries", "php/exceptions", "php/interfaces"] + "classmap": ["project/libraries", "php/libraries", "php/exceptions", "php/interfaces"] } } From deb1a812d0086547cb1ff83aec600d0456266f1f Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 7 Sep 2017 13:54:24 -0400 Subject: [PATCH 181/214] Remove moment dependency --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 841f66c0f7e..e4641b701b5 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "jison": "^0.4.17", "mocha": "3.4.2", "mocha-webpack": "0.7.0", - "moment": "^2.18.1", "react-checkbox-group": "3.1.1", "react-radio-group": "3.0.2", "webpack": "1.14.0", From 14494651d08d3a95fe488cda188a889797417794 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 7 Sep 2017 14:03:50 -0400 Subject: [PATCH 182/214] Rename NAIPTestSuite --- test/phpunit.xml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/phpunit.xml b/test/phpunit.xml index db21cf8c812..ba08d4f933d 100644 --- a/test/phpunit.xml +++ b/test/phpunit.xml @@ -4,12 +4,9 @@ verbose="true" colors="true"> - - ./unittests/ParserTest.php - - - + ./unittests/NDB_BVL_Instrument_JSON.php + ./unittests/ParserTest.php From 796b08cd3c132a6c27e090334bdc40a6885eae74 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 7 Sep 2017 14:08:45 -0400 Subject: [PATCH 183/214] Recompile JS --- modules/dicom_archive/js/dicom_archive.js | 4 ++-- modules/survey_accounts/js/columnFormatter.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/dicom_archive/js/dicom_archive.js b/modules/dicom_archive/js/dicom_archive.js index d485a240b6b..cbe62f77e9c 100644 --- a/modules/dicom_archive/js/dicom_archive.js +++ b/modules/dicom_archive/js/dicom_archive.js @@ -1,2 +1,2 @@ -!function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={exports:{},id:moduleId,loaded:!1};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.loaded=!0,module.exports}var installedModules={};return __webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.p="",__webpack_require__(0)}([function(module,exports,__webpack_require__){"use strict";function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(self,call){if(!self)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!call||"object"!=typeof call&&"function"!=typeof call?self:call}function _inherits(subClass,superClass){if("function"!=typeof superClass&&null!==superClass)throw new TypeError("Super expression must either be null or a function, not "+typeof superClass);subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor:{value:subClass,enumerable:!1,writable:!0,configurable:!0}}),superClass&&(Object.setPrototypeOf?Object.setPrototypeOf(subClass,superClass):subClass.__proto__=superClass)}var _createClass=function(){function defineProperties(target,props){for(var i=0;i-1)return null;var row={};if(rowHeaders.forEach(function(header,index){row[header]=rowData[index]},this),"Metadata"===column){var metadataURL=loris.BaseURL+"/dicom_archive/viewDetails/?tarchiveID="+row.TarchiveID;return React.createElement("td",null,React.createElement("a",{href:metadataURL},cell))}if("MRI Browser"===column){if(null===row.SessionID||""===row.SessionID)return React.createElement("td",null," ");var mrlURL=loris.BaseURL+"/imaging_browser/viewSession/?sessionID="+row.SessionID;return React.createElement("td",null,React.createElement("a",{href:mrlURL},cell))}return"INVALID - HIDDEN"===cell?React.createElement("td",{className:"text-danger"},cell):React.createElement("td",null,cell)}Object.defineProperty(exports,"__esModule",{value:!0}),window.formatColumn=formatColumn,exports.default=formatColumn}]); -//# sourceMappingURL=dicom_archive.js.map +!function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={exports:{},id:moduleId,loaded:!1};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.loaded=!0,module.exports}var installedModules={};return __webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.p="",__webpack_require__(0)}({0:function(module,exports,__webpack_require__){"use strict";function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(self,call){if(!self)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!call||"object"!=typeof call&&"function"!=typeof call?self:call}function _inherits(subClass,superClass){if("function"!=typeof superClass&&null!==superClass)throw new TypeError("Super expression must either be null or a function, not "+typeof superClass);subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor:{value:subClass,enumerable:!1,writable:!0,configurable:!0}}),superClass&&(Object.setPrototypeOf?Object.setPrototypeOf(subClass,superClass):subClass.__proto__=superClass)}var _createClass=function(){function defineProperties(target,props){for(var i=0;i-1)return null;var row={};if(rowHeaders.forEach(function(header,index){row[header]=rowData[index]},this),"Metadata"===column){var metadataURL=loris.BaseURL+"/dicom_archive/viewDetails/?tarchiveID="+row.TarchiveID;return React.createElement("td",null,React.createElement("a",{href:metadataURL},cell))}if("MRI Browser"===column){if(null===row.SessionID||""===row.SessionID)return React.createElement("td",null," ");var mrlURL=loris.BaseURL+"/imaging_browser/viewSession/?sessionID="+row.SessionID;return React.createElement("td",null,React.createElement("a",{href:mrlURL},cell))}return"INVALID - HIDDEN"===cell?React.createElement("td",{className:"text-danger"},cell):React.createElement("td",null,cell)}Object.defineProperty(exports,"__esModule",{value:!0}),window.formatColumn=formatColumn,exports.default=formatColumn}}); +//# sourceMappingURL=dicom_archive.js.map \ No newline at end of file diff --git a/modules/survey_accounts/js/columnFormatter.js b/modules/survey_accounts/js/columnFormatter.js index 20035ef5099..4ee665be670 100644 --- a/modules/survey_accounts/js/columnFormatter.js +++ b/modules/survey_accounts/js/columnFormatter.js @@ -1,2 +1,2 @@ -!function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={exports:{},id:moduleId,loaded:!1};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.loaded=!0,module.exports}var installedModules={};return __webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.p="",__webpack_require__(0)}([function(module,exports){"use strict";function formatColumn(column,cell,rowData,rowHeaders){var row={};if(rowHeaders.forEach(function(header,index){row[header]=rowData[index]},this),"URL"===column){var url=loris.BaseURL+"/survey-react.php?key="+row.URL;return React.createElement("td",null,React.createElement("a",{href:url},cell))}return React.createElement("td",null,cell)}Object.defineProperty(exports,"__esModule",{value:!0}),window.formatColumn=formatColumn,exports.default=formatColumn}]); +!function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={exports:{},id:moduleId,loaded:!1};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.loaded=!0,module.exports}var installedModules={};return __webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.p="",__webpack_require__(0)}([function(module,exports){"use strict";function formatColumn(column,cell,rowData,rowHeaders){var row={};if(rowHeaders.forEach(function(header,index){row[header]=rowData[index]},this),"URL"===column){var url=loris.BaseURL+"/survey.php?key="+row.URL;return React.createElement("td",null,React.createElement("a",{href:url},cell))}return React.createElement("td",null,cell)}Object.defineProperty(exports,"__esModule",{value:!0}),window.formatColumn=formatColumn,exports.default=formatColumn}]); //# sourceMappingURL=columnFormatter.js.map \ No newline at end of file From 339e9d1f5ba4f3843adfc0a1070807731f35d4f7 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Fri, 8 Sep 2017 10:20:14 -0400 Subject: [PATCH 184/214] Address PR comments for instrument docs --- docs/API/InstrumentFormat.md | 42 +++++++----------------------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/docs/API/InstrumentFormat.md b/docs/API/InstrumentFormat.md index b6ad773d5b9..2ba3803db50 100644 --- a/docs/API/InstrumentFormat.md +++ b/docs/API/InstrumentFormat.md @@ -28,13 +28,12 @@ Instrument format: { "Meta" : { "InstrumentVersion": string, - "InstrumentFormatVersion" : "v0.0.2-dev", + "InstrumentFormatVersion" : "v0.0.3-dev", "ShortName" : "InstrumentName", /* Required */ "LongName": { - "en-ca": "The Human Readable Instrument Name", - "fr-ca": "Bonjour" + "en-CA": "The Human Readable Instrument Name", + "fr-CA": "Le nom de l'instrument lisible par l'homme" }, /* An object keyed by language - Required */ - "SupportedLanguages": array(string), "IncludeMetaDataFields" : boolean }, "Elements" : [ PageElements ] @@ -145,7 +144,7 @@ as follows. It denotes a group of values of which the user must select one optio "Description" : REQUIRED, "Options" : { "Values" : { - 'en-ca': { + 'en-CA': { "SaveValue" : "Human Readable Description", "SaveValue2" : "Another human readable description" ... @@ -318,32 +317,6 @@ the user. It has the following form. "Type": "score", "Name": REQUIRED, "Description": OPTIONAL, - "Options": { - /* None currently */ - } -} -``` - -`Type`: MUST be "score". - -`Name`: Required. Follows `PageElement.Name` rules. The Name MAY be used by an - implementation as a field name to save calculated data into. - -`Description`: Optional. Follows `PageElement.Name` rules. If not specified, the - score will be displayed with no accompagning text. - - -### 2.1.6: CalcFieldElement - -A calc field represents a placeholder to display/save values based on other -data entered by the user in the instrument but does not directly get input from -the user. It has the following form. - -```js -{ - "Type": "calc", - "Name": REQUIRED, - "Description": OPTIONAL, "Formula": string, "Options": { /* None currently */ @@ -361,7 +334,8 @@ the user. It has the following form. `Formula`: A string of LorisScript which will determine the displayed value of this field. -### 2.1.7: RadioElement + +### 2.1.6: RadioElement A Radio element represents a HTML radio type and appears as follows. It denotes a group of values of which the user must select one option. @@ -373,14 +347,14 @@ It denotes a group of values of which the user must select one option. "Description" : REQUIRED, "Options" : { "Values" : { - 'en-ca': { + 'en-CA': { "SaveValue" : "Human Readable Description", "SaveValue2" : "Another human readable description" ... } }, "AllowMultiple" : boolean, - "RequireResponse" : boolean || string, + "RequireResponse" : boolean|string, "Hidden": boolean, "HiddenSurvey": boolean } From 62bcbb57add6aa3ab2c40134d989147aac8a637a Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Fri, 8 Sep 2017 10:29:55 -0400 Subject: [PATCH 185/214] Parser README update --- jsx/lib/Parser/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsx/lib/Parser/README.md b/jsx/lib/Parser/README.md index 24b24662ce3..8f64aa94337 100644 --- a/jsx/lib/Parser/README.md +++ b/jsx/lib/Parser/README.md @@ -1,4 +1,4 @@ -# ZAIN Rules +# LorisScript [JS Use](#js)
[PHP Use](#php)
From cd5b7a1614cfefe06975a06514567ba19eeb4561 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Fri, 8 Sep 2017 11:13:56 -0400 Subject: [PATCH 186/214] Address most PR comments --- jsx/lib/localize-instrument.js | 2 +- php/libraries/Evaluator.class.inc | 160 +++++++++--------- php/libraries/Functions.php | 2 +- php/libraries/Lexer.class.inc | 118 ++++++------- php/libraries/NDB_BVL_Instrument.class.inc | 2 +- .../NDB_BVL_Instrument_JSON.class.inc | 14 +- 6 files changed, 146 insertions(+), 152 deletions(-) diff --git a/jsx/lib/localize-instrument.js b/jsx/lib/localize-instrument.js index 5c7a30446e6..3cec2e25fe7 100644 --- a/jsx/lib/localize-instrument.js +++ b/jsx/lib/localize-instrument.js @@ -1,4 +1,4 @@ -function localizeInstrument(rawInstrument, lang = 'en-ca') { +function localizeInstrument(rawInstrument, lang = 'en-CA') { const instrument = JSON.parse(JSON.stringify(rawInstrument)); try { diff --git a/php/libraries/Evaluator.class.inc b/php/libraries/Evaluator.class.inc index 898a95b48ad..6246d916d6f 100644 --- a/php/libraries/Evaluator.class.inc +++ b/php/libraries/Evaluator.class.inc @@ -1,92 +1,92 @@ exp(1), - 'PI' => pi(), - 'false' => false, - 'true' => true, - 'null' => null, - ); - switch($tree['tag']) { - case 'String': - case 'EString': - return substr((string)$tree['args'][0],1,-1); - case 'Number': - return floatval($tree['args'][0]); - case 'Constant': - return $constants[$tree['args'][0]]; - case 'Variable': - if (isset($scope[$tree['args'][0]])) { - return $scope[$tree['args'][0]]; - } - throw new \Exception("Unbound variable: " . $tree['args'][0]); - case 'NestedVariable': - if (isset($scope[$tree['args'][0]])) { - $res = $scope[$tree['args'][0]]; - $i = 0; - for($i; $i < count($tree['args'][1]); $i++) { - if (!isset($res[$tree['args'][1][$i]])) { - throw new \Exception("Unbound sub-variable: " . $tree['args'][1][$i]); - } - $res = $res[$tree['args'][1][$i]]; - } - return $res; - } - throw new \Exception("Unbound variable: " . $tree['args'][0]); - case 'FuncApplication': - if ($tree['args'][0] == 'if') { - if (static::evalAST($tree['args'][1][0], $scope)) { - return static::evalAST($tree['args'][1][1], $scope); + $evalarg = function($a) use ($scope) { + return static::evalAST($a, $scope); + }; + $constants = array( + 'E' => exp(1), + 'PI' => pi(), + 'false' => false, + 'true' => true, + 'null' => null, + ); + switch($tree['tag']) { + case 'String': + case 'EString': + return substr((string)$tree['args'][0],1,-1); + case 'Number': + return floatval($tree['args'][0]); + case 'Constant': + return $constants[$tree['args'][0]]; + case 'Variable': + if (isset($scope[$tree['args'][0]])) { + return $scope[$tree['args'][0]]; + } + throw new \Exception("Unbound variable: " . $tree['args'][0]); + case 'NestedVariable': + if (isset($scope[$tree['args'][0]])) { + $res = $scope[$tree['args'][0]]; + $i = 0; + for($i; $i < count($tree['args'][1]); $i++) { + if (!isset($res[$tree['args'][1][$i]])) { + throw new \Exception("Unbound sub-variable: " . $tree['args'][1][$i]); } - return static::evalAST($tree['args'][1][2], $scope); + $res = $res[$tree['args'][1][$i]]; } - $funcName = '_'.$tree['args'][0]; - if (!isset($FUNCTIONS[$funcName])) { - throw new \Exception(`{$tree['args'][0]} is not a defined function.`); + return $res; + } + throw new \Exception("Unbound variable: " . $tree['args'][0]); + case 'FuncApplication': + if ($tree['args'][0] == 'if') { + if (static::evalAST($tree['args'][1][0], $scope)) { + return static::evalAST($tree['args'][1][1], $scope); } - $funcArgs = array_map($evalarg, $tree['args'][1]); - return $FUNCTIONS[$funcName](...$funcArgs); - case 'NestedExpression': - return static::evalAST($tree['args'][0], $scope); - case 'UnaryOp': - $funcName = '_'.$tree['op']; - return $UNARY_OPS[$funcName](static::evalAST($tree['args'][0], $scope)); - case 'BinaryOp': - $funcArgs = array_map($evalarg, $tree['args']); - $funcName = '_'.$tree['op']; - return $BINARY_OPS[$funcName](...$funcArgs); - } + return static::evalAST($tree['args'][1][2], $scope); + } + $funcName = '_'.$tree['args'][0]; + if (!isset($FUNCTIONS[$funcName])) { + throw new \Exception(`{$tree['args'][0]} is not a defined function.`); + } + $funcArgs = array_map($evalarg, $tree['args'][1]); + return $FUNCTIONS[$funcName](...$funcArgs); + case 'NestedExpression': + return static::evalAST($tree['args'][0], $scope); + case 'UnaryOp': + $funcName = '_'.$tree['op']; + return $UNARY_OPS[$funcName](static::evalAST($tree['args'][0], $scope)); + case 'BinaryOp': + $funcArgs = array_map($evalarg, $tree['args']); + $funcName = '_'.$tree['op']; + return $BINARY_OPS[$funcName](...$funcArgs); + } + } + static function evaluate($expression, $scope) { + if(!isset($expression)) { + return null; + } + if($expression === '') { + return ''; } - static function evaluate($expression, $scope) { - if(!isset($expression)) { - return null; - } - if($expression === '') { - return ''; - } - try { - $tree = (new Parser($expression))->parse(); - } catch (\Exception $e) { - throw new \Exception("Parser error; review Syntax\n$e"); - } - $res = static::evalAST($tree, $scope); - return $res; + try { + $tree = (new Parser($expression))->parse(); + } catch (\Exception $e) { + throw new \Exception("Parser error; review Syntax\n$e"); } + $res = static::evalAST($tree, $scope); + return $res; } +} - // For testing: - // php evaluator.php 'eq([x][z(0)], 33)' - // var_dump(Evaluator::evaluate($argv[1], array('x' => array('z' => 33)))); +// For testing: +// php evaluator.php 'eq([x][z(0)], 33)' +// var_dump(Evaluator::evaluate($argv[1], array('x' => array('z' => 33)))); ?> diff --git a/php/libraries/Functions.php b/php/libraries/Functions.php index 35b7d2d0a02..3e5784e6723 100644 --- a/php/libraries/Functions.php +++ b/php/libraries/Functions.php @@ -1,6 +1,6 @@ "null", - "/^true/" => "true", - "/^false/" => "false", - "/^E/" => "E", - "/^PI/" => "PI", - "/^\d+(\.\d+)?\b/" => "NUMBER", - "/^[*]/" => "*", - "/^\//" => "/", - "/^[-]/" => "-", - "/^[+]/" => "+", - "/^\^/" => "^", - "/^[=]/" => "=", - "/^[!]/" => "!", - "/^[%]/" => "%", - "/^\(/" => "(", - "/^\)/" => ")", - "/^[,]/" => ",", - "/^\<\>/" => "<>", - "/^\<[=]/" => "<=", - "/^\>[=]/" => ">=", - "/^\ "<", - "/^\>/" => ">", - "/^and/" => "and", - "/^or/" => "or", - "/^not/" => "not", - "/^[_a-zA-Z0-9]\w*/" => "VARIABLE", - "/^\"[^\"]*\"/" => "ESTRING", - "/^\'[^\']*\'/" => "STRING", - "/^\[/" => "[", - "/^\]/" => "]", - "/^[\t ]*/" => null, // Skip spaces and tabs - ); - static function match($expression, $offset) { - $substr = substr($expression, $offset); - foreach(static::$terminals as $pattern => $token) { - if (preg_match($pattern, $substr, $matches)) { - return array( - 'match' => $matches[0], - 'token' => $token, - ); - } +class Lexer { + private static $terminals = array( + "/^null/" => "null", + "/^true/" => "true", + "/^false/" => "false", + "/^E/" => "E", + "/^PI/" => "PI", + "/^\d+(\.\d+)?\b/" => "NUMBER", + "/^[*]/" => "*", + "/^\//" => "/", + "/^[-]/" => "-", + "/^[+]/" => "+", + "/^\^/" => "^", + "/^[=]/" => "=", + "/^[!]/" => "!", + "/^[%]/" => "%", + "/^\(/" => "(", + "/^\)/" => ")", + "/^[,]/" => ",", + "/^\<\>/" => "<>", + "/^\<[=]/" => "<=", + "/^\>[=]/" => ">=", + "/^\ "<", + "/^\>/" => ">", + "/^and/" => "and", + "/^or/" => "or", + "/^not/" => "not", + "/^[_a-zA-Z0-9]\w*/" => "VARIABLE", + "/^\"[^\"]*\"/" => "ESTRING", + "/^\'[^\']*\'/" => "STRING", + "/^\[/" => "[", + "/^\]/" => "]", + "/^[\t ]*/" => null, // Skip spaces and tabs + ); + static function match($expression, $offset) { + $substr = substr($expression, $offset); + foreach(static::$terminals as $pattern => $token) { + if (preg_match($pattern, $substr, $matches)) { + return array( + 'match' => $matches[0], + 'token' => $token, + ); } - return false; } - static function lex($expression) { - $tokens = array(); - $offset = 0; - while($offset < strlen($expression)) { - $matched = static::match($expression, $offset); - if ($matched === false) { - throw new Exception("Unexpected token after: " . substr($expression, $offset)); - } - // Skip spaces and tabs - if ($matched['token'] !== null) { - $tokens[] = $matched; - } - $offset += strlen($matched['match']); + return false; + } + static function lex($expression) { + $tokens = array(); + $offset = 0; + while($offset < strlen($expression)) { + $matched = static::match($expression, $offset); + if ($matched === false) { + throw new Exception("Unexpected token after: " . substr($expression, $offset)); + } + // Skip spaces and tabs + if ($matched['token'] !== null) { + $tokens[] = $matched; } - return $tokens; + $offset += strlen($matched['match']); } + return $tokens; } +} ?> diff --git a/php/libraries/NDB_BVL_Instrument.class.inc b/php/libraries/NDB_BVL_Instrument.class.inc index c7ad665fb72..ef42bf5f29d 100644 --- a/php/libraries/NDB_BVL_Instrument.class.inc +++ b/php/libraries/NDB_BVL_Instrument.class.inc @@ -424,7 +424,7 @@ class NDB_BVL_Instrument extends NDB_Page $formArray['tableID'] = "instrument"; } - $formArray['lang'] = $_REQUEST["lang"] ? $_REQUEST["lang"] : "en-ca"; + $formArray['lang'] = $_REQUEST["lang"] ?? "en-CA"; $smarty->assign('form', $formArray); $html = $smarty->fetch("directentry_form.tpl"); } elseif (isset($_REQUEST['json']) == 'true') { diff --git a/php/libraries/NDB_BVL_Instrument_JSON.class.inc b/php/libraries/NDB_BVL_Instrument_JSON.class.inc index d7a54a2dd17..7e393e4a221 100644 --- a/php/libraries/NDB_BVL_Instrument_JSON.class.inc +++ b/php/libraries/NDB_BVL_Instrument_JSON.class.inc @@ -11,7 +11,6 @@ * @link https://www.github.com/aces/Loris-Trunk/ */ namespace Loris\Behavioural; -use \Exception; /** * JSON Instrument class * @@ -68,12 +67,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument } function _getLang() { - //$timepoint = \TimePoint::singleton($this->getSessionID()); - //$candID = $timepoint->getCandID(); - //$candidate = \Candidate::singleton($candID); - //$lang = $candidate->getCandidateLanguage(); - $lang = "en-ca"; - return $lang ? $lang : "en-ca"; + return "en-CA"; } @@ -82,7 +76,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument "SELECT * FROM $this->table WHERE CommentID=:CID", array('CID' => $this->getCommentID()) ); - return $result ? $result[0] : array(); + return $result; } /** @@ -166,7 +160,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument $db =& \Database::singleton(); $smarty = new \Smarty_NeuroDB(); $smarty->assign('instrumentJSON', htmlspecialchars($this->toJSON())); - $smarty->assign('initialData', htmlspecialchars(json_encode($this->_getInstrumentData($db)))); + $smarty->assign('initialData', htmlspecialchars(json_encode(\NDB_BVL_Instrument::loadInstanceData($this)))); $smarty->assign('context', htmlspecialchars(json_encode($this->_getContext()))); $smarty->assign('lang', htmlspecialchars($this->_getLang())); $html = $smarty->fetch("instrument_react.tpl"); @@ -216,7 +210,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument array_push($encounteredVars, $key); $res = self::referenceTree($formula, $formulaMap, $encounteredVars); if($res !== false) - throw new Exception ("Circular reference at $res"); + throw new \Exception ("Circular reference at $res"); } } From 026fe0abec8b043087499c02851fa34f98cc88d8 Mon Sep 17 00:00:00 2001 From: Zain Virani Date: Fri, 8 Sep 2017 12:06:27 -0400 Subject: [PATCH 187/214] font size change (#28) --- htdocs/css/direct-entry.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/css/direct-entry.css b/htdocs/css/direct-entry.css index 467f8b6f76d..8a7ceca6060 100644 --- a/htdocs/css/direct-entry.css +++ b/htdocs/css/direct-entry.css @@ -34,7 +34,7 @@ label { .studyTitle { color: white; - font-size: 28px; + font-size: 24px; vertical-align: bottom; line-height: 1; } From b998b27d26d32cdff685f44cf5734aa9819e2238 Mon Sep 17 00:00:00 2001 From: Zain Virani Date: Fri, 8 Sep 2017 12:08:23 -0400 Subject: [PATCH 188/214] stylistic changes to save button (#27) --- htdocs/css/direct-entry.css | 11 +++++++++++ jsx/InstrumentForm.js | 15 +++++++++------ jsx/InstrumentFormContainer.js | 26 +++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/htdocs/css/direct-entry.css b/htdocs/css/direct-entry.css index 8a7ceca6060..9980c60390c 100644 --- a/htdocs/css/direct-entry.css +++ b/htdocs/css/direct-entry.css @@ -50,3 +50,14 @@ label { align-items: center; font-size: 2em; } + +#save { + margin: auto; + display: block; + color: white; + background-color: #08245b; +} + +#warning { + color: red; +} diff --git a/jsx/InstrumentForm.js b/jsx/InstrumentForm.js index d4b426f76e2..d39aab2053c 100644 --- a/jsx/InstrumentForm.js +++ b/jsx/InstrumentForm.js @@ -7,7 +7,7 @@ const { SelectElement, RadioGroupLabels, RadioGroupElement, CheckboxGroupElement * The meta and elements passed to this component must already be 'localized' * (see ./lib/localize-instrument). */ -const InstrumentForm = ({meta, elements, showRequired, errorMessage, onUpdate, onSave}) => { +const InstrumentForm = ({meta, elements, showRequired, errorMessage, onUpdate, onSave, saveText, saveWarning}) => { return (
@@ -19,7 +19,7 @@ const InstrumentForm = ({meta, elements, showRequired, errorMessage, onUpdate, o renderElement(element, index, onUpdate, showRequired && element.Options.RequireResponse) )) } - +
); }; @@ -145,11 +145,14 @@ function renderDate(element, key, onUpdate, isRequired) { ) } -const SaveButton = ({onClick}) => { +const SaveButton = ({onClick, saveText, saveWarning}) => { return ( - +
+ +

{saveWarning}

+
); } diff --git a/jsx/InstrumentFormContainer.js b/jsx/InstrumentFormContainer.js index 5c2658606da..a72d2e871c3 100644 --- a/jsx/InstrumentFormContainer.js +++ b/jsx/InstrumentFormContainer.js @@ -17,14 +17,32 @@ class InstrumentFormContainer extends React.Component { data: this.props.initialData, localizedInstrument: localizeInstrument(this.props.instrument, this.props.lang), showRequired: false, - errorMessage: null + errorMessage: null, }; - + this.updateInstrumentData = this.updateInstrumentData.bind(this); this.incompleteRequiredFieldExists = this.incompleteRequiredFieldExists.bind(this); this.onSaveButtonClick = this.onSaveButtonClick.bind(this); } + getSaveText(lang) { + switch(lang) { + case 'en-ca': + return 'Save'; + case 'fr-ca': + return 'Enregistrer'; + } + } + + getSaveWarning(lang) { + switch(lang) { + case 'en-ca': + return 'You cannot modify your answers after clicking this button. Please ensure all answers are correct.'; + case 'fr-ca': + return 'Vous ne pouvez pas modifier vos réponses après avoir cliqué sur ce bouton. Assurez-vous que toutes les réponses sont correctes.'; + } + } + /** * This function is called when the user inputs or updates an instrument * field. It is responsible for updating `state.data`, which involves not @@ -186,7 +204,7 @@ class InstrumentFormContainer extends React.Component { render() { const { data, localizedInstrument } = this.state; - const { context, options } = this.props; + const { context, options, lang } = this.props; return ( ); } From 0312a48540243a8d179c48bd701577d455e8199d Mon Sep 17 00:00:00 2001 From: zainvirani Date: Fri, 8 Sep 2017 13:24:43 -0400 Subject: [PATCH 189/214] fixed center tag --- jsx/InstrumentForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsx/InstrumentForm.js b/jsx/InstrumentForm.js index d39aab2053c..e8c894486ea 100644 --- a/jsx/InstrumentForm.js +++ b/jsx/InstrumentForm.js @@ -151,7 +151,7 @@ const SaveButton = ({onClick, saveText, saveWarning}) => { -

{saveWarning}

+

{saveWarning}

); } From e5e501258ad2a8fd88850961a2a0cb2bca84caf3 Mon Sep 17 00:00:00 2001 From: Zain Virani Date: Mon, 11 Sep 2017 18:26:56 -0400 Subject: [PATCH 190/214] Update README.md --- jsx/lib/Parser/README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/jsx/lib/Parser/README.md b/jsx/lib/Parser/README.md index 8f64aa94337..86fae4c3cfc 100644 --- a/jsx/lib/Parser/README.md +++ b/jsx/lib/Parser/README.md @@ -1,5 +1,6 @@ # LorisScript +[Formula Building](#formula)
[JS Use](#js)
[PHP Use](#php)
[Development](#dev)
@@ -18,8 +19,31 @@ This ReadMe breaks down the different parts of the Parser and lists syntax rules Note that this parser is made up of two separate components: a JS parser and a PHP parser, both of which use the same syntax detailed below. +# Formula Building +When building a formula, be sure to reference the [Syntax](#syntax) section. + +Starting simply, basic mathematical notation such as addition, subtraction, etc..., follows standard notation. Similarly, syntax such as parentheses and semantics such as order of operatitraction, etc..., follows standard notation. + +Some examples of standard calculations include totalling a set of fields, calculating their mean, calculating date differences, and if statements. In these examples, we will also be covering variable reference, and function nesting. + +### Example: Functions and variable access: +First of all, to refer to a context variable, use `[variable_name]`. For nested variables, use `[context][variable_name]`. For array access, use `[variable(index)]`. + +Now, to sum a set of fields, say `q1`, through `q5`, use `sum(q1,q2,q3,q4,q5)`. This is an example of a function. The sum function takes at least one parameter, with no upper limit. + +To calculate the mean, similarly apply the mean function: `mean(q1,q2,q3,q4,q5)`. + +For date difference calculations, use the `datediff()` function. Say the candidate's birthday is stored in `[context][dob]`. Then, let's assume we use January 6th, 1996 as a standard example date. To calculate the difference we would use `datediff('1996-01-06', [context][dob], $U, $F)`. This function will return `1996-01-06` - `[context][dob]`. `$U` is the unit that will be returned, Y for year, M for month, and D for day. Lowercase can also be used. Notice the ISO standard date format YYYY-MM-DD. `$F` is a sign flag. If false, the result will always be positive, i.e. if the candidate's date of birth is before January 6th, 1996, then the function would retrun `[context][dob]` - `1996-01-06`. If true, the result will always be `1996-01-06` - `[context][dob]`, even if the result is negative. The sign flag is not required. + +Function nesting is as simple as passing a function as an argument of another function. Say we calculate the candidate's age in months using `datediff([testDate], [context][dob], 'M')`. Now we want to check if the user's age in months is greater than 56. We could assign the candidate's age in a calculated field, and then reference it, or we can skip the middleman! We could use the `if()` function: `if(datediff([testDate], [context][dob], 'M')>56, $val1, $val2)`. If the 1st parameter is true, i.e. if the candidate is older than 56 months, `$val1` will be returned. If the candidate is younger than or exactly 56 months old, `$val2` will be returned. + +As mentioned before, other basic math can be computed. Make sure to see the [Syntax](#syntax) section to take advantage of all of the functions and logic available to use. + +### Example Two: Ne + # JS Use -At the top of your JS file add `import { Evaluator } from 'Parser';` (change the path based on your directory location). +At the top of your JS file add `import { Evaluator } from 'Parser';` (change the path based on your directons are also standard notation.
+Starting simply, basic mathematical notation such as addition, subory location).
Call `Evaluator(LOGIC_STRING, SCOPE)` to evaluate an equation. # PHP Use From 65ae5d7e88a96d97c03aa6cde515c2e8c15a18e4 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Tue, 12 Sep 2017 14:22:32 -0400 Subject: [PATCH 191/214] Never require hidden elements --- jsx/InstrumentFormContainer.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/jsx/InstrumentFormContainer.js b/jsx/InstrumentFormContainer.js index a72d2e871c3..6dd3f9e646a 100644 --- a/jsx/InstrumentFormContainer.js +++ b/jsx/InstrumentFormContainer.js @@ -88,7 +88,12 @@ class InstrumentFormContainer extends React.Component { */ incompleteRequiredFieldExists() { const annotatedElements = this.annotateElements( - this.state.localizedInstrument.Elements, + this.filterElements( + this.state.localizedInstrument.Elements, + this.state.data, + this.props.context, + this.props.options.surveyMode + ), this.state.data, this.props.context ); @@ -166,7 +171,11 @@ class InstrumentFormContainer extends React.Component { const errorDiv = document.getElementById("instrument-error"); errorDiv.scrollIntoView(false); } else { - this.props.onSave(this.state.data) + this.props.onSave(this.state.data); + this.setState({ + errorMessage: '', + showRequired: false + }); } } From b919661622da26d9fdba3f4ae383fecf3aa4c4ae Mon Sep 17 00:00:00 2001 From: Zain Virani Date: Tue, 12 Sep 2017 14:54:22 -0400 Subject: [PATCH 192/214] insert to test_names (#34) * insert to test_names * forgot a line --- tools/generate_instrument_schemas.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/generate_instrument_schemas.php b/tools/generate_instrument_schemas.php index 63c1386209e..0e1dcfbc3cb 100644 --- a/tools/generate_instrument_schemas.php +++ b/tools/generate_instrument_schemas.php @@ -29,7 +29,8 @@ function generateSchema($elements) { //Handle meta info $metaInfo = $elements['Meta']; - $tableName = $metaInfo['ShortName']; + $tableName = htmlspecialchars($metaInfo['ShortName']); + $fullName = htmlspecialchars($metaInfo['LongName']); $output = ''; $output .= "DROP TABLE IF EXISTS `$tableName`;\n"; @@ -105,7 +106,8 @@ function generateSchema($elements) $output .= "`$elName` $type DEFAULT NULL,\n"; } - $output .= "PRIMARY KEY (`CommentID`))"; + $output .= "PRIMARY KEY (`CommentID`));\n"; + $output .= "INSERT INTO test_names (Test_name, Full_name, Sub_group, IsDirectEntry) VALUES ('$tableName', \"$fullName\", 1, 1);"; return $output; } From 1c8e3b7828638fa460caed985d44eb71da07c38d Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Wed, 13 Sep 2017 15:30:17 -0400 Subject: [PATCH 193/214] Fix namespaces, fix indentation --- php/libraries/Lexer.class.inc | 2 + .../NDB_BVL_Instrument_JSON.class.inc | 42 +- php/libraries/Parser.class.inc | 687 +++++++++--------- 3 files changed, 386 insertions(+), 345 deletions(-) diff --git a/php/libraries/Lexer.class.inc b/php/libraries/Lexer.class.inc index 813bdc308c8..41122f81297 100644 --- a/php/libraries/Lexer.class.inc +++ b/php/libraries/Lexer.class.inc @@ -1,6 +1,8 @@ "null", diff --git a/php/libraries/NDB_BVL_Instrument_JSON.class.inc b/php/libraries/NDB_BVL_Instrument_JSON.class.inc index 7e393e4a221..4496df772c7 100644 --- a/php/libraries/NDB_BVL_Instrument_JSON.class.inc +++ b/php/libraries/NDB_BVL_Instrument_JSON.class.inc @@ -11,6 +11,8 @@ * @link https://www.github.com/aces/Loris-Trunk/ */ namespace Loris\Behavioural; + +use LORIS\LorisScript\Evaluator; /** * JSON Instrument class * @@ -103,6 +105,32 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument } } + function setOptions($options = array()) { + $this->options = $options; + } + + static function isDisplayed($element, $values, $context, $surveyMode) { + if ( + ($element["Hidden"]) || + ($surveyMode && $element["HiddenSurvey"]) || + ($element.DisplayIf === false) + ) { + return false; + } + + if ($element["DisplayIf" === ""]) return true; + + $displayIf = $element["DisplayIf"]; + + return Evaluator::evaluate($displayIf, array_merge($values, $context)); + } + + static function filterElements($elements, $values, $context, $surveyMode) { + return array_filter($elements, function($element) use ($values, $context, $surveyMode) { + return self::isDisplayed($element, $values, $context, $surveyMode); + }); + } + static function isRequired($element, $values, $context) { $inputTypes = ['select', 'date', 'radio', 'text', 'calc', 'checkbox']; @@ -113,12 +141,20 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument if (is_bool($requireResponse)) return $requireResponse; - return \LorisScript\Evaluator::evaluate($requireResponse, array_merge($values, $context)); + return Evaluator::evaluate($requireResponse, array_merge($values, $context)); } function incompleteExists($values) { $incompleteExists = false; - foreach ($this->instrument["Elements"] as &$element) { + $context = $this->_getContext(); + $visibleElements = self::filterElements( + $this->instrument["Elements"], + $values, + $context, + $this->options["surveyMode"] + ); + + foreach ($visibleElements as &$element) { if (isset($values[$element["Name"]])) { continue; } else { @@ -135,7 +171,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument foreach ($this->instrument["Elements"] as &$element) { if ($element["Type"] == 'calc') { - $calculatedValues[$element["Name"]] = \LorisScript\Evaluator::evaluate($element["Formula"], array_merge($values, $this->_getContext())); + $calculatedValues[$element["Name"]] = Evaluator::evaluate($element["Formula"], array_merge($values, $this->_getContext())); } } diff --git a/php/libraries/Parser.class.inc b/php/libraries/Parser.class.inc index 0139b751518..93bd9e098df 100644 --- a/php/libraries/Parser.class.inc +++ b/php/libraries/Parser.class.inc @@ -1,383 +1,386 @@ expression = $expression; - $this->tokens = Lexer::lex($expression); - $this->offset = 0; - } - function parse() { - $ast = $this->parseBoolTerm(); - if ($ast === false) { - throw new Exception("Cannot parse expression."); - } - if ($this->offset < count($this->tokens)) { - throw new Exception("Unexpected token(s) after: " . substr($this->expression, $this->offset)); - } - return $ast; +namespace LORIS\LorisScript; + +use \Exception; + +class Parser { + private $expression; + private $tokens; + private $offset; + function __construct($expression) { + $this->expression = $expression; + $this->tokens = Lexer::lex($expression); + $this->offset = 0; + } + function parse() { + $ast = $this->parseBoolTerm(); + if ($ast === false) { + throw new Exception("Cannot parse expression."); } - function next() { - $this->offset += 1; - if ($this->offset > count($this->tokens)) { - throw new Exception("Invalid expression ending: " . $this->expression); - } + if ($this->offset < count($this->tokens)) { + throw new Exception("Unexpected token(s) after: " . substr($this->expression, $this->offset)); } - function getBacktrack() { - return $this->offset; + return $ast; + } + function next() { + $this->offset += 1; + if ($this->offset > count($this->tokens)) { + throw new Exception("Invalid expression ending: " . $this->expression); } - function backtrack($backtrack) { - $this->offset = $backtrack; + } + function getBacktrack() { + return $this->offset; + } + function backtrack($backtrack) { + $this->offset = $backtrack; + } + function expect($token) { + if ($this->offset >= count($this->tokens)) { + return false; } - function expect($token) { - if ($this->offset >= count($this->tokens)) { - return false; - } - $currentTok = $this->tokens[$this->offset]; - if ($currentTok['token'] !== $token) { - return false; - } - return $currentTok['match']; - } - function parseBoolTerm() { - $bt = $this->getBacktrack(); - $left = $this->parseUnaryBool(); - $filt = array_values(array_filter(["and","or"], array($this, 'expect'))); - if (count($filt) === 0) { - return $left; - } - $this->next(); - $op = $filt[0]; - $right = $this->parseBoolTerm(); - if ($right === false) { - $this->backtrack($bt); - return false; - } - return array( - 'tag' => 'BinaryOp', - 'op' => $op, - 'args' => array($left, $right), - ); + $currentTok = $this->tokens[$this->offset]; + if ($currentTok['token'] !== $token) { + return false; } - function parseUnaryBool() { - $bt = $this->getBacktrack(); - $op = $this->expect("not"); - if ($op === false) { - return $this->parseBoolComp(); - } - $this->next(); - $arg = $this->parseBoolComp(); - if ($arg === false) { - $this->backtrack($bt); - return false; - } - return array( - 'tag' => 'UnaryOp', - 'op' => $op, - 'args' => array($arg), - ); + return $currentTok['match']; + } + function parseBoolTerm() { + $bt = $this->getBacktrack(); + $left = $this->parseUnaryBool(); + $filt = array_values(array_filter(["and","or"], array($this, 'expect'))); + if (count($filt) === 0) { + return $left; } - function parseBoolComp() { - $bt = $this->getBacktrack(); - $left = $this->parseNumTerm(); - $filt = array_values(array_filter(["=", "<", ">", "<>", "<=", ">="], array($this, 'expect'))); - if (count($filt) === 0) { - return $left; - } - $this->next(); - $op = $filt[0]; - $right = $this->parseBoolComp(); - if ($right === false) { - $this->backtrack($bt); - return false; - } - return array( - 'tag' => 'BinaryOp', - 'op' => $op, - 'args' => array($left, $right), - ); + $this->next(); + $op = $filt[0]; + $right = $this->parseBoolTerm(); + if ($right === false) { + $this->backtrack($bt); + return false; } - function parseNumTerm() { - $bt = $this->getBacktrack(); - $left = $this->parseNumFactor(); - $filt = array_values(array_filter(["+", "-"], array($this, 'expect'))); - if (count($filt) === 0) { - return $left; - } - $this->next(); - $op = $filt[0]; - $right = $this->parseNumTerm(); - if ($right === false) { - $this->backtrack($bt); - return false; - } - return array( - 'tag' => 'BinaryOp', - 'op' => $op, - 'args' => array($left, $right), - ); + return array( + 'tag' => 'BinaryOp', + 'op' => $op, + 'args' => array($left, $right), + ); + } + function parseUnaryBool() { + $bt = $this->getBacktrack(); + $op = $this->expect("not"); + if ($op === false) { + return $this->parseBoolComp(); } - function parseNumFactor() { - $bt = $this->getBacktrack(); - $left = $this->parseNumPower(); - $filt = array_values(array_filter(["*", "/"], array($this, 'expect'))); - if (count($filt) === 0) { - return $left; - } - $this->next(); - $op = $filt[0]; - $right = $this->parseNumFactor(); - if ($right === false) { - $this->backtrack($bt); - return false; - } - return array( - 'tag' => 'BinaryOp', - 'op' => $op, - 'args' => array($left, $right), - ); + $this->next(); + $arg = $this->parseBoolComp(); + if ($arg === false) { + $this->backtrack($bt); + return false; } - function parseNumPower() { - $bt = $this->getBacktrack(); - $left = $this->parseUnaryFact(); - $op = $this->expect("^"); - if ($op === false) { - return $left; - } - $this->next(); - $right = $this->parseNumPower(); - if ($right === false) { - $this->backtrack($bt); - return false; - } - return array( - 'tag' => 'BinaryOp', - 'op' => $op, - 'args' => array($left, $right), - ); + return array( + 'tag' => 'UnaryOp', + 'op' => $op, + 'args' => array($arg), + ); + } + function parseBoolComp() { + $bt = $this->getBacktrack(); + $left = $this->parseNumTerm(); + $filt = array_values(array_filter(["=", "<", ">", "<>", "<=", ">="], array($this, 'expect'))); + if (count($filt) === 0) { + return $left; } - function parseUnaryFact() { - $arg = $this->parseUnaryPercent(); - $op = $this->expect("!"); - if ($op === false) { - return $arg; - } - $this->next(); - return array( - 'tag' => 'UnaryOp', - 'op' => $op, - 'args' => array($arg), - ); + $this->next(); + $op = $filt[0]; + $right = $this->parseBoolComp(); + if ($right === false) { + $this->backtrack($bt); + return false; } - function parseUnaryPercent() { - $arg = $this->parseUnaryMinus(); - $op = $this->expect("%"); - if ($op === false) { - return $arg; - } - $this->next(); - return array( - 'tag' => 'UnaryOp', - 'op' => $op, - 'args' => array($arg), - ); + return array( + 'tag' => 'BinaryOp', + 'op' => $op, + 'args' => array($left, $right), + ); + } + function parseNumTerm() { + $bt = $this->getBacktrack(); + $left = $this->parseNumFactor(); + $filt = array_values(array_filter(["+", "-"], array($this, 'expect'))); + if (count($filt) === 0) { + return $left; } - function parseUnaryMinus() { - $op = $this->expect("-"); - if ($op === false) { - return $this->parseTerminal(); - } - $this->next(); - $arg = $this->parseTerminal(); - return array( - 'tag' => 'UnaryOp', - 'op' => $op, - 'args' => array($arg), - ); + $this->next(); + $op = $filt[0]; + $right = $this->parseNumTerm(); + if ($right === false) { + $this->backtrack($bt); + return false; } - function parseTerminal() { - return ( - $this->parseFuncCall() ?: - $this->parseVariable() ?: - $this->parseNested() ?: - $this->parseNumber() ?: - $this->parseString() ?: - $this->parseConstant() - ); + return array( + 'tag' => 'BinaryOp', + 'op' => $op, + 'args' => array($left, $right), + ); + } + function parseNumFactor() { + $bt = $this->getBacktrack(); + $left = $this->parseNumPower(); + $filt = array_values(array_filter(["*", "/"], array($this, 'expect'))); + if (count($filt) === 0) { + return $left; } - function parseNested() { - $bt = $this->getBacktrack(); - if ($this->expect("(") === false) { - return false; - } - $this->next(); - $expr = $this->parseBoolTerm(); - if ($expr === false) { - $this->backtrack($bt); - return false; - } - if ($this->expect(")") === false) { - $this->backtrack($bt); - return false; - } - $this->next(); - return $expr; + $this->next(); + $op = $filt[0]; + $right = $this->parseNumFactor(); + if ($right === false) { + $this->backtrack($bt); + return false; } - function parseNumber() { - $num = $this->expect("NUMBER"); - if ($num === false) { - return false; - } - $this->next(); - return array( - 'tag' => 'Number', - 'args' => array($num), - ); + return array( + 'tag' => 'BinaryOp', + 'op' => $op, + 'args' => array($left, $right), + ); + } + function parseNumPower() { + $bt = $this->getBacktrack(); + $left = $this->parseUnaryFact(); + $op = $this->expect("^"); + if ($op === false) { + return $left; + } + $this->next(); + $right = $this->parseNumPower(); + if ($right === false) { + $this->backtrack($bt); + return false; } - function parseString() { - $str = $this->expect("STRING"); + return array( + 'tag' => 'BinaryOp', + 'op' => $op, + 'args' => array($left, $right), + ); + } + function parseUnaryFact() { + $arg = $this->parseUnaryPercent(); + $op = $this->expect("!"); + if ($op === false) { + return $arg; + } + $this->next(); + return array( + 'tag' => 'UnaryOp', + 'op' => $op, + 'args' => array($arg), + ); + } + function parseUnaryPercent() { + $arg = $this->parseUnaryMinus(); + $op = $this->expect("%"); + if ($op === false) { + return $arg; + } + $this->next(); + return array( + 'tag' => 'UnaryOp', + 'op' => $op, + 'args' => array($arg), + ); + } + function parseUnaryMinus() { + $op = $this->expect("-"); + if ($op === false) { + return $this->parseTerminal(); + } + $this->next(); + $arg = $this->parseTerminal(); + return array( + 'tag' => 'UnaryOp', + 'op' => $op, + 'args' => array($arg), + ); + } + function parseTerminal() { + return ( + $this->parseFuncCall() ?: + $this->parseVariable() ?: + $this->parseNested() ?: + $this->parseNumber() ?: + $this->parseString() ?: + $this->parseConstant() + ); + } + function parseNested() { + $bt = $this->getBacktrack(); + if ($this->expect("(") === false) { + return false; + } + $this->next(); + $expr = $this->parseBoolTerm(); + if ($expr === false) { + $this->backtrack($bt); + return false; + } + if ($this->expect(")") === false) { + $this->backtrack($bt); + return false; + } + $this->next(); + return $expr; + } + function parseNumber() { + $num = $this->expect("NUMBER"); + if ($num === false) { + return false; + } + $this->next(); + return array( + 'tag' => 'Number', + 'args' => array($num), + ); + } + function parseString() { + $str = $this->expect("STRING"); + if ($str === false) { + $str =$this->expect("ESTRING"); if ($str === false) { - $str =$this->expect("ESTRING"); - if ($str === false) { - return false; - } - $this->next(); - return array( - 'tag' => 'EString', - 'args' => array($str), - ); + return false; } $this->next(); return array( - 'tag' => 'String', + 'tag' => 'EString', 'args' => array($str), ); } - function parseConstant() { - $filt = array_values(array_filter(["false", "true", "null", "E", "PI"], array($this, 'expect'))); - if (count($filt) === 0) { - return false; - } - $this->next(); - return array( - 'tag' => 'Constant', - 'args' => array($filt[0]), - ); + $this->next(); + return array( + 'tag' => 'String', + 'args' => array($str), + ); + } + function parseConstant() { + $filt = array_values(array_filter(["false", "true", "null", "E", "PI"], array($this, 'expect'))); + if (count($filt) === 0) { + return false; } - function parseFuncCall() { - $bt = $this->getBacktrack(); - $func = $this->expect("VARIABLE"); - if ($func === false) { - return false; - } - $this->next(); - if ($this->expect("(") === false) { - $this->backtrack($bt); - return false; - } - $this->next(); - $args = $this->parseArguments(); - if ($this->expect(")") === false) { - $this->backtrack($bt); - return false; - } - $this->next(); - return array( - 'tag' => 'FuncApplication', - 'args' => array($func, $args), - ); + $this->next(); + return array( + 'tag' => 'Constant', + 'args' => array($filt[0]), + ); + } + function parseFuncCall() { + $bt = $this->getBacktrack(); + $func = $this->expect("VARIABLE"); + if ($func === false) { + return false; } - function parseArguments() { - $bt = $this->getBacktrack(); - $arg = $this->parseBoolTerm(); - if ($arg === false) { - return []; - } - if ($this->expect(",") === false) { - return [$arg]; - } - $this->next(); - $nextArgs = $this->parseArguments(); - if ($nextArgs === false) { - $this->backtrack($bt); - return false; - } - return array_merge([$arg], $nextArgs);; + $this->next(); + if ($this->expect("(") === false) { + $this->backtrack($bt); + return false; } - function parseVariable() { - $sym = $this->parseVarSymbol(); - if ($sym === false) { - return false; + $this->next(); + $args = $this->parseArguments(); + if ($this->expect(")") === false) { + $this->backtrack($bt); + return false; + } + $this->next(); + return array( + 'tag' => 'FuncApplication', + 'args' => array($func, $args), + ); + } + function parseArguments() { + $bt = $this->getBacktrack(); + $arg = $this->parseBoolTerm(); + if ($arg === false) { + return []; + } + if ($this->expect(",") === false) { + return [$arg]; + } + $this->next(); + $nextArgs = $this->parseArguments(); + if ($nextArgs === false) { + $this->backtrack($bt); + return false; + } + return array_merge([$arg], $nextArgs);; + } + function parseVariable() { + $sym = $this->parseVarSymbol(); + if ($sym === false) { + return false; + } + $var = $sym['var']; + $accessors = isset($sym['num']) ? [$sym['num']] : []; + while ($access = $this->parseVarSymbol()) { + if (isset($access['var'])) { + $accessors[] = $access['var']; } - $var = $sym['var']; - $accessors = isset($sym['num']) ? [$sym['num']] : []; - while ($access = $this->parseVarSymbol()) { - if (isset($access['var'])) { - $accessors[] = $access['var']; - } - if (isset($access['num'])) { - $accessors[] = $access['num']; - } - }; - if (count($accessors) === 0) { - return array( - 'tag' => 'Variable', - 'args' => array($var), - ); + if (isset($access['num'])) { + $accessors[] = $access['num']; } + }; + if (count($accessors) === 0) { return array( - 'tag' => 'NestedVariable', - 'args' => array($var, $accessors), + 'tag' => 'Variable', + 'args' => array($var), ); } - function parseVarSymbol() { - $bt = $this->getBacktrack(); - if ($this->expect("[") === false) { - return false; - } - $this->next(); - $var = $this->expect("VARIABLE"); - if ($var === false) { - $this->backtrack($bt); - return false; - } - $this->next(); - $nest = $this->parseNestedVar(); - if ($this->expect("]") === false) { - $this->backtrack($bt); - return false; - } - $this->next(); - if ($nest === false) { - return array('var' => $var); - } - return array('var' => $var, 'num' => $nest); + return array( + 'tag' => 'NestedVariable', + 'args' => array($var, $accessors), + ); + } + function parseVarSymbol() { + $bt = $this->getBacktrack(); + if ($this->expect("[") === false) { + return false; } - function parseNestedVar() { - $bt = $this->getBacktrack(); - if ($this->expect("(") === false) { - $this->backtrack($bt); - return false; - } - $this->next(); - $nest = $this->expect("NUMBER"); + $this->next(); + $var = $this->expect("VARIABLE"); + if ($var === false) { + $this->backtrack($bt); + return false; + } + $this->next(); + $nest = $this->parseNestedVar(); + if ($this->expect("]") === false) { + $this->backtrack($bt); + return false; + } + $this->next(); + if ($nest === false) { + return array('var' => $var); + } + return array('var' => $var, 'num' => $nest); + } + function parseNestedVar() { + $bt = $this->getBacktrack(); + if ($this->expect("(") === false) { + $this->backtrack($bt); + return false; + } + $this->next(); + $nest = $this->expect("NUMBER"); + if ($nest === false) { + $nest = $this->expect("VARIABLE"); if ($nest === false) { - $nest = $this->expect("VARIABLE"); - if ($nest === false) { - $this->backtrack($bt); - return false; - } - } - $this->next(); - if ($this->expect(")") === false) { $this->backtrack($bt); return false; } - $this->next(); - return $nest; } + $this->next(); + if ($this->expect(")") === false) { + $this->backtrack($bt); + return false; + } + $this->next(); + return $nest; } +} ?> From 151f1eb75d1534632481c4ce10efbac129f45d0d Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Wed, 13 Sep 2017 15:31:04 -0400 Subject: [PATCH 194/214] Set survey mode in react-survey.php --- htdocs/survey-react.php | 1 + 1 file changed, 1 insertion(+) diff --git a/htdocs/survey-react.php b/htdocs/survey-react.php index fab10ba1acb..efe99ad7fa7 100644 --- a/htdocs/survey-react.php +++ b/htdocs/survey-react.php @@ -252,6 +252,7 @@ function display() $isDataSubmission = isset($_POST['instrumentData']); $instrument = new NDB_BVL_Instrument_JSON(); $instrument->setup($this->CommentID); + $instrument->setOptions(array('surveyMode' => true)); $workspace = $this->caller->load( $this->TestName, From bad54eee635bc6b882b4d5ed26f6b25cc0cb10bd Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Wed, 13 Sep 2017 17:03:05 -0400 Subject: [PATCH 195/214] Fix <= bug and add tests --- php/libraries/Functions.php | 2 +- test/unittests/ParserTest.php | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/php/libraries/Functions.php b/php/libraries/Functions.php index 3e5784e6723..78eb2340414 100644 --- a/php/libraries/Functions.php +++ b/php/libraries/Functions.php @@ -30,7 +30,7 @@ function getFunctions() { return $a >= $b; }, '_leq' => function ($a, $b) { - return $a >= $b; + return $a <= $b; }, '_add' => function ($a, $b) { return $a + $b; diff --git a/test/unittests/ParserTest.php b/test/unittests/ParserTest.php index dd5f1995c4d..b465c315768 100644 --- a/test/unittests/ParserTest.php +++ b/test/unittests/ParserTest.php @@ -1,6 +1,6 @@ assertEquals($res, $expected); } + + public function testLessThanOrEqualTo(){ + $equation = '1 <= 2'; + $expected = true; + + try { + $res = Evaluator::evaluate($equation, $this->scope); + var_dump($res); + } catch (Exception $e) { + $res = "$e"; + } + $this->assertEquals($res, $expected); + } + + public function testLessThanOrEqualToAgain(){ + $equation = '2 <= 1'; + $expected = false; + + try { + $res = Evaluator::evaluate($equation, $this->scope); + var_dump($res); + } catch (Exception $e) { + $res = "$e"; + } + $this->assertEquals($res, $expected); + } } From 8947d13681fb2ff45141e57110c7d9212986c59b Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Wed, 13 Sep 2017 17:04:59 -0400 Subject: [PATCH 196/214] Move ParserTests into own testsuite --- test/phpunit.xml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/phpunit.xml b/test/phpunit.xml index ba08d4f933d..fc2c38a7ab8 100644 --- a/test/phpunit.xml +++ b/test/phpunit.xml @@ -5,8 +5,11 @@ colors="true"> - ./unittests/NDB_BVL_Instrument_JSON.php - ./unittests/ParserTest.php + ./unittests/NDB_BVL_Instrument_JSON.php + + + + ./unittests/ParserTest.php From 8f4dbb91230cad5c7d4a5beb0b186647c3788521 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Wed, 13 Sep 2017 17:05:14 -0400 Subject: [PATCH 197/214] Fix context issue --- php/libraries/NDB_BVL_Instrument_JSON.class.inc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/php/libraries/NDB_BVL_Instrument_JSON.class.inc b/php/libraries/NDB_BVL_Instrument_JSON.class.inc index 4496df772c7..80373d61358 100644 --- a/php/libraries/NDB_BVL_Instrument_JSON.class.inc +++ b/php/libraries/NDB_BVL_Instrument_JSON.class.inc @@ -78,7 +78,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument "SELECT * FROM $this->table WHERE CommentID=:CID", array('CID' => $this->getCommentID()) ); - return $result; + return $result[0]; } /** @@ -122,7 +122,9 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument $displayIf = $element["DisplayIf"]; - return Evaluator::evaluate($displayIf, array_merge($values, $context)); + $scope = array_merge($values, array("context" => $context)); + $res = Evaluator::evaluate($displayIf, $scope); + return $res; } static function filterElements($elements, $values, $context, $surveyMode) { @@ -141,7 +143,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument if (is_bool($requireResponse)) return $requireResponse; - return Evaluator::evaluate($requireResponse, array_merge($values, $context)); + return Evaluator::evaluate($requireResponse, array_merge($values, array("context" => $context))); } function incompleteExists($values) { @@ -171,7 +173,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument foreach ($this->instrument["Elements"] as &$element) { if ($element["Type"] == 'calc') { - $calculatedValues[$element["Name"]] = Evaluator::evaluate($element["Formula"], array_merge($values, $this->_getContext())); + $calculatedValues[$element["Name"]] = Evaluator::evaluate($element["Formula"], array_merge($values, array("context" => $this->_getContext()))); } } From 66b8944795b4e98b0129efc33382c3ce8cc31b88 Mon Sep 17 00:00:00 2001 From: Zain Virani Date: Thu, 14 Sep 2017 11:17:37 -0400 Subject: [PATCH 198/214] reset radio button (#36) * reset radio * refactor * fixed padding * Update InstrumentForm.js * fixed indentation, key, for loop --- htdocs/css/direct-entry.css | 8 ++++++++ jsx/InstrumentForm.js | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/htdocs/css/direct-entry.css b/htdocs/css/direct-entry.css index 9980c60390c..30bafd44760 100644 --- a/htdocs/css/direct-entry.css +++ b/htdocs/css/direct-entry.css @@ -61,3 +61,11 @@ label { #warning { color: red; } + +.asText { + background: none; + border: none; + margin: 0; + padding-bottom: 15px; + text-decoration: underline; +} diff --git a/jsx/InstrumentForm.js b/jsx/InstrumentForm.js index e8c894486ea..01ac27b4911 100644 --- a/jsx/InstrumentForm.js +++ b/jsx/InstrumentForm.js @@ -65,8 +65,8 @@ function renderRadioLabels(element, key) { function renderRadio(element, key, onUpdate, isRequired) { return ( +
+ +
); } From f8594b48718c90751de2544fa978de98e499a5e0 Mon Sep 17 00:00:00 2001 From: Zain Virani Date: Tue, 12 Sep 2017 14:54:25 -0400 Subject: [PATCH 199/214] Cap gen schema fix (#33) * insert into test_names * special char fix --- tools/generate_instrument_schemas.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/generate_instrument_schemas.php b/tools/generate_instrument_schemas.php index 0e1dcfbc3cb..261cb847241 100644 --- a/tools/generate_instrument_schemas.php +++ b/tools/generate_instrument_schemas.php @@ -30,7 +30,7 @@ function generateSchema($elements) //Handle meta info $metaInfo = $elements['Meta']; $tableName = htmlspecialchars($metaInfo['ShortName']); - $fullName = htmlspecialchars($metaInfo['LongName']); + $fullName = htmlspecialchars($metaInfo['LongName']['en-ca']); $output = ''; $output .= "DROP TABLE IF EXISTS `$tableName`;\n"; From 20b9f4dff624e9de4d404a7f5bde87a6e23d03b3 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 11:40:33 -0400 Subject: [PATCH 200/214] Remove format check from NDB_Page --- php/libraries/NDB_Page.class.inc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/php/libraries/NDB_Page.class.inc b/php/libraries/NDB_Page.class.inc index 4f5c0096334..50d4efb7053 100644 --- a/php/libraries/NDB_Page.class.inc +++ b/php/libraries/NDB_Page.class.inc @@ -590,14 +590,6 @@ class NDB_Page */ function display() { - if (isset($_REQUEST['format'])) { - switch($_REQUEST['format']) { - case 'json': - header('Content-Type: application/json'); - return $this->toJSON(); - } - } - if ($this->skipTemplate) { return ""; } From 5732227dc6e5c7979299c9da8b3b227aa6ca35d9 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 11:50:25 -0400 Subject: [PATCH 201/214] Rename calc field to score field --- jsx/InstrumentForm.js | 6 +++--- jsx/InstrumentFormContainer.js | 8 ++++---- jsx/lib/localize-instrument.js | 4 ++-- php/libraries/NDB_BVL_Instrument_JSON.class.inc | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/jsx/InstrumentForm.js b/jsx/InstrumentForm.js index 01ac27b4911..8638979dd98 100644 --- a/jsx/InstrumentForm.js +++ b/jsx/InstrumentForm.js @@ -45,8 +45,8 @@ function renderElement(element, key, onUpdate, required = false) { return renderCheckbox(element, key, onUpdate, required) } else if (element.Type === 'text') { return renderText(element, key, onUpdate, required) - } else if (element.Type === 'calc') { - return renderCalc(element, key, onUpdate) + } else if (element.Type === 'score') { + return renderScore(element, key, onUpdate) } else if (element.Type === 'date') { return renderDate(element, key, onUpdate, required) } @@ -126,7 +126,7 @@ function renderText(element, key, onUpdate, isRequired) { ); } -function renderCalc(element, key, onUpdate) { +function renderScore(element, key, onUpdate) { return ( (element.Type === 'calc') + const scoreElements = this.props.instrument.Elements.filter( + (element) => (element.Type === 'score') ); const evaluatorContext = { ...instrumentData, context: this.props.context }; - const calculatedValues = calcElements.reduce((result, element) => { + const calculatedValues = scoreElements.reduce((result, element) => { try { result[element.Name] = String(Evaluator(element.Formula, evaluatorContext)); } catch (e) { diff --git a/jsx/lib/localize-instrument.js b/jsx/lib/localize-instrument.js index 3cec2e25fe7..d28fad65998 100644 --- a/jsx/lib/localize-instrument.js +++ b/jsx/lib/localize-instrument.js @@ -7,7 +7,7 @@ function localizeInstrument(rawInstrument, lang = 'en-CA') { const convertedElements = []; instrument['Elements'].forEach((element) => { - if (['label', 'text', 'calc', 'date', 'select', 'radio', 'checkbox'].includes(element.Type)) { + if (['label', 'text', 'score', 'date', 'select', 'radio', 'checkbox'].includes(element.Type)) { if (element['Description'][lang]) { element['Description'] = element['Description'][lang]; } else { @@ -17,7 +17,7 @@ function localizeInstrument(rawInstrument, lang = 'en-CA') { element['Description'] = "Choose a value: "; } else if (['label'].includes(element.Type)) { element['Description'] = ""; - } else if (['calc'].includes(element.Type)) { + } else if (['score'].includes(element.Type)) { element['Description'] = "Result: "; } } diff --git a/php/libraries/NDB_BVL_Instrument_JSON.class.inc b/php/libraries/NDB_BVL_Instrument_JSON.class.inc index 80373d61358..8440d35f060 100644 --- a/php/libraries/NDB_BVL_Instrument_JSON.class.inc +++ b/php/libraries/NDB_BVL_Instrument_JSON.class.inc @@ -134,7 +134,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument } static function isRequired($element, $values, $context) { - $inputTypes = ['select', 'date', 'radio', 'text', 'calc', 'checkbox']; + $inputTypes = ['select', 'date', 'radio', 'text', 'score', 'checkbox']; if (!in_array($element["Type"], $inputTypes)) return false; @@ -172,7 +172,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument $calculatedValues = array(); foreach ($this->instrument["Elements"] as &$element) { - if ($element["Type"] == 'calc') { + if ($element["Type"] == 'score') { $calculatedValues[$element["Name"]] = Evaluator::evaluate($element["Formula"], array_merge($values, array("context" => $this->_getContext()))); } } @@ -275,7 +275,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument static function inlineCalcFormulas($elements) { $calcElements = array_filter($elements, function ($element) { - return $element["Type"] === 'calc'; + return $element["Type"] === 'score'; }); $formulaMap = array_reduce($calcElements, function ($result, $element) { @@ -286,7 +286,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument self::checkForCircularRefs($formulaMap); return array_map(function ($element) use ($formulaMap) { - if ($element["Type"] !== 'calc') { return $element; } + if ($element["Type"] !== 'score') { return $element; } $recusivelyInlinedFormula = $element["Formula"]; $resultIsSame = false; while (!$resultIsSame) { From 37679cd4ad82823fa682f6092c0553fe169e4a0f Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 11:53:00 -0400 Subject: [PATCH 202/214] Update instrument schema script --- tools/generate_instrument_schemas.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/generate_instrument_schemas.php b/tools/generate_instrument_schemas.php index 261cb847241..0f9b6912890 100644 --- a/tools/generate_instrument_schemas.php +++ b/tools/generate_instrument_schemas.php @@ -30,7 +30,7 @@ function generateSchema($elements) //Handle meta info $metaInfo = $elements['Meta']; $tableName = htmlspecialchars($metaInfo['ShortName']); - $fullName = htmlspecialchars($metaInfo['LongName']['en-ca']); + $fullName = htmlspecialchars($metaInfo['LongName']['en-CA']); $output = ''; $output .= "DROP TABLE IF EXISTS `$tableName`;\n"; @@ -60,7 +60,7 @@ function generateSchema($elements) $elName = htmlspecialchars($element['Name']); switch($switchType) { case "radio": - $enumOpts = $element['Options']['Values']['en-ca']; + $enumOpts = $element['Options']['Values']['en-CA']; if(isset($element['Options']['AllowMultiple'])) { $allowMult = $element['Options']['AllowMultiple']; } else { @@ -73,7 +73,7 @@ function generateSchema($elements) } break; case "select": - $enumOpts = $element['Options']['Values']['en-ca']; + $enumOpts = $element['Options']['Values']['en-CA']; if(isset($element['Options']['AllowMultiple'])) { $allowMult = $element['Options']['AllowMultiple']; } else { @@ -91,7 +91,7 @@ function generateSchema($elements) case "checkbox": $type = "TEXT"; break; - case "calc": + case "score": $type = "TEXT"; break; case "date": From a84d8ee4ce9ee8699f49b785282a22cf39fe1efe Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 12:26:36 -0400 Subject: [PATCH 203/214] Add back ->setup call --- php/libraries/NDB_BVL_Instrument.class.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/php/libraries/NDB_BVL_Instrument.class.inc b/php/libraries/NDB_BVL_Instrument.class.inc index ef42bf5f29d..af13556b514 100644 --- a/php/libraries/NDB_BVL_Instrument.class.inc +++ b/php/libraries/NDB_BVL_Instrument.class.inc @@ -216,6 +216,7 @@ class NDB_BVL_Instrument extends NDB_Page // Now go ahead and instantiate it $obj = new $class; // Now go ahead and instantiate it + $success = $obj->setup($commentID, $page); } // Adds all of the form element and form rules to the page after From 6cb06191f9859ade73825bbf2ec39f5ffc90d52e Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 12:41:46 -0400 Subject: [PATCH 204/214] Fix lint issues --- php/libraries/Evaluator.class.inc | 162 +++++++++++++++++++----------- php/libraries/Lexer.class.inc | 127 +++++++++++++++-------- 2 files changed, 188 insertions(+), 101 deletions(-) diff --git a/php/libraries/Evaluator.class.inc b/php/libraries/Evaluator.class.inc index 6246d916d6f..a7666a846c5 100644 --- a/php/libraries/Evaluator.class.inc +++ b/php/libraries/Evaluator.class.inc @@ -1,79 +1,123 @@ + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ + namespace LORIS\LorisScript; -require_once("Functions.php"); +require_once "Functions.php"; -class Evaluator { - static function evalAST($tree, $scope) { - $FUNCTIONS = getFunctions(); - $UNARY_OPS = getUnary(); +/** + * Evaluator + * + * @category Main + * @package Behavioural + * @author Unknown + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ +class Evaluator +{ + /** + * TODO: + * + * @param tree $tree TODO + * @param scope $scope TODO + * + * @return TODO + */ + static function evalAST($tree, $scope) + { + $FUNCTIONS = getFunctions(); + $UNARY_OPS = getUnary(); $BINARY_OPS = getBinary(); - $evalarg = function($a) use ($scope) { + $evalarg = function ($a) use ($scope) { return static::evalAST($a, $scope); }; $constants = array( - 'E' => exp(1), - 'PI' => pi(), - 'false' => false, - 'true' => true, - 'null' => null, - ); + 'E' => exp(1), + 'PI' => pi(), + 'false' => false, + 'true' => true, + 'null' => null, + ); + switch($tree['tag']) { - case 'String': - case 'EString': - return substr((string)$tree['args'][0],1,-1); - case 'Number': - return floatval($tree['args'][0]); - case 'Constant': - return $constants[$tree['args'][0]]; - case 'Variable': - if (isset($scope[$tree['args'][0]])) { - return $scope[$tree['args'][0]]; - } - throw new \Exception("Unbound variable: " . $tree['args'][0]); - case 'NestedVariable': - if (isset($scope[$tree['args'][0]])) { - $res = $scope[$tree['args'][0]]; - $i = 0; - for($i; $i < count($tree['args'][1]); $i++) { - if (!isset($res[$tree['args'][1][$i]])) { - throw new \Exception("Unbound sub-variable: " . $tree['args'][1][$i]); - } - $res = $res[$tree['args'][1][$i]]; + case 'String': + case 'EString': + return substr((string)$tree['args'][0], 1, -1); + case 'Number': + return floatval($tree['args'][0]); + case 'Constant': + return $constants[$tree['args'][0]]; + case 'Variable': + if (isset($scope[$tree['args'][0]])) { + return $scope[$tree['args'][0]]; + } + throw new \Exception("Unbound variable: " . $tree['args'][0]); + case 'NestedVariable': + if (isset($scope[$tree['args'][0]])) { + $res = $scope[$tree['args'][0]]; + $i = 0; + for ($i; $i < count($tree['args'][1]); $i++) { + if (!isset($res[$tree['args'][1][$i]])) { + throw new \Exception( + "Unbound sub-variable: " . $tree['args'][1][$i] + ); } - return $res; + $res = $res[$tree['args'][1][$i]]; } - throw new \Exception("Unbound variable: " . $tree['args'][0]); - case 'FuncApplication': - if ($tree['args'][0] == 'if') { - if (static::evalAST($tree['args'][1][0], $scope)) { - return static::evalAST($tree['args'][1][1], $scope); - } - return static::evalAST($tree['args'][1][2], $scope); - } - $funcName = '_'.$tree['args'][0]; - if (!isset($FUNCTIONS[$funcName])) { - throw new \Exception(`{$tree['args'][0]} is not a defined function.`); + return $res; + } + throw new \Exception("Unbound variable: " . $tree['args'][0]); + case 'FuncApplication': + if ($tree['args'][0] == 'if') { + if (static::evalAST($tree['args'][1][0], $scope)) { + return static::evalAST($tree['args'][1][1], $scope); } - $funcArgs = array_map($evalarg, $tree['args'][1]); - return $FUNCTIONS[$funcName](...$funcArgs); - case 'NestedExpression': - return static::evalAST($tree['args'][0], $scope); - case 'UnaryOp': - $funcName = '_'.$tree['op']; - return $UNARY_OPS[$funcName](static::evalAST($tree['args'][0], $scope)); - case 'BinaryOp': - $funcArgs = array_map($evalarg, $tree['args']); - $funcName = '_'.$tree['op']; - return $BINARY_OPS[$funcName](...$funcArgs); + return static::evalAST($tree['args'][1][2], $scope); + } + $funcName = '_'.$tree['args'][0]; + if (!isset($FUNCTIONS[$funcName])) { + throw new \Exception("{$tree['args'][0]} is not a defined function"); + } + $funcArgs = array_map($evalarg, $tree['args'][1]); + return $FUNCTIONS[$funcName](...$funcArgs); + case 'NestedExpression': + return static::evalAST($tree['args'][0], $scope); + case 'UnaryOp': + $funcName = '_'.$tree['op']; + return $UNARY_OPS[$funcName](static::evalAST($tree['args'][0], $scope)); + case 'BinaryOp': + $funcArgs = array_map($evalarg, $tree['args']); + $funcName = '_'.$tree['op']; + return $BINARY_OPS[$funcName](...$funcArgs); } } - static function evaluate($expression, $scope) { - if(!isset($expression)) { + + /** + * TODO: + * + * @param expression $expression TODO + * @param scope $scope TODO + * + * @return TODO + */ + static function evaluate($expression, $scope) + { + if (!isset($expression)) { return null; } - if($expression === '') { + if ($expression === '') { return ''; } try { diff --git a/php/libraries/Lexer.class.inc b/php/libraries/Lexer.class.inc index 41122f81297..d1cbcb0e6b6 100644 --- a/php/libraries/Lexer.class.inc +++ b/php/libraries/Lexer.class.inc @@ -1,61 +1,104 @@ + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ + namespace LORIS\LorisScript; use \Exception; -class Lexer { - private static $terminals = array( - "/^null/" => "null", - "/^true/" => "true", - "/^false/" => "false", - "/^E/" => "E", - "/^PI/" => "PI", - "/^\d+(\.\d+)?\b/" => "NUMBER", - "/^[*]/" => "*", - "/^\//" => "/", - "/^[-]/" => "-", - "/^[+]/" => "+", - "/^\^/" => "^", - "/^[=]/" => "=", - "/^[!]/" => "!", - "/^[%]/" => "%", - "/^\(/" => "(", - "/^\)/" => ")", - "/^[,]/" => ",", - "/^\<\>/" => "<>", - "/^\<[=]/" => "<=", - "/^\>[=]/" => ">=", - "/^\ "<", - "/^\>/" => ">", - "/^and/" => "and", - "/^or/" => "or", - "/^not/" => "not", - "/^[_a-zA-Z0-9]\w*/" => "VARIABLE", - "/^\"[^\"]*\"/" => "ESTRING", - "/^\'[^\']*\'/" => "STRING", - "/^\[/" => "[", - "/^\]/" => "]", - "/^[\t ]*/" => null, // Skip spaces and tabs - ); - static function match($expression, $offset) { +/** + * Lexer + * + * @category Main + * @package Behavioural + * @author Unknown + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ +class Lexer +{ + private static $_terminals = array( + "/^null/" => "null", + "/^true/" => "true", + "/^false/" => "false", + "/^E/" => "E", + "/^PI/" => "PI", + "/^\d+(\.\d+)?\b/" => "NUMBER", + "/^[*]/" => "*", + "/^\//" => "/", + "/^[-]/" => "-", + "/^[+]/" => "+", + "/^\^/" => "^", + "/^[=]/" => "=", + "/^[!]/" => "!", + "/^[%]/" => "%", + "/^\(/" => "(", + "/^\)/" => ")", + "/^[,]/" => ",", + "/^\<\>/" => "<>", + "/^\<[=]/" => "<=", + "/^\>[=]/" => ">=", + "/^\ "<", + "/^\>/" => ">", + "/^and/" => "and", + "/^or/" => "or", + "/^not/" => "not", + "/^[_a-zA-Z0-9]\w*/" => "VARIABLE", + "/^\"[^\"]*\"/" => "ESTRING", + "/^\'[^\']*\'/" => "STRING", + "/^\[/" => "[", + "/^\]/" => "]", + // Skip spaces and tabs + "/^[\t ]*/" => null, + ); + /** + * TODO: + * + * @param expression $expression TODO + * @param offset $offset TODO + * + * @return TODO + */ + static function match($expression, $offset) + { $substr = substr($expression, $offset); - foreach(static::$terminals as $pattern => $token) { + foreach (static::$_terminals as $pattern => $token) { if (preg_match($pattern, $substr, $matches)) { return array( - 'match' => $matches[0], - 'token' => $token, - ); + 'match' => $matches[0], + 'token' => $token, + ); } } return false; } - static function lex($expression) { + + /** + * TODO: + * + * @param expression $expression TODO + * + * @return TODO + */ + static function lex($expression) + { $tokens = array(); $offset = 0; - while($offset < strlen($expression)) { + while ($offset < strlen($expression)) { $matched = static::match($expression, $offset); if ($matched === false) { - throw new Exception("Unexpected token after: " . substr($expression, $offset)); + throw new Exception( + "Unexpected token after: " . substr($expression, $offset) + ); } // Skip spaces and tabs if ($matched['token'] !== null) { From 3e32895f73c16bee01f77384323dac88510c6ca6 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 13:07:50 -0400 Subject: [PATCH 205/214] Add comment placeholders to Parser --- php/libraries/Parser.class.inc | 178 ++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 4 deletions(-) diff --git a/php/libraries/Parser.class.inc b/php/libraries/Parser.class.inc index 93bd9e098df..a95a6b5054e 100644 --- a/php/libraries/Parser.class.inc +++ b/php/libraries/Parser.class.inc @@ -1,18 +1,50 @@ + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ namespace LORIS\LorisScript; use \Exception; - -class Parser { +/** + * Parser + * + * @category Main + * @package Behavioural + * @author Unknown + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ +class Parser +{ private $expression; private $tokens; private $offset; + + /** + * TODO: + * + * @return TODO + */ function __construct($expression) { $this->expression = $expression; - $this->tokens = Lexer::lex($expression); - $this->offset = 0; + $this->tokens = Lexer::lex($expression); + $this->offset = 0; } + + /** + * TODO: + * + * @return TODO + */ function parse() { $ast = $this->parseBoolTerm(); if ($ast === false) { @@ -23,18 +55,42 @@ class Parser { } return $ast; } + + /** + * TODO: + * + * @return TODO + */ function next() { $this->offset += 1; if ($this->offset > count($this->tokens)) { throw new Exception("Invalid expression ending: " . $this->expression); } } + + /** + * TODO: + * + * @return TODO + */ function getBacktrack() { return $this->offset; } + + /** + * TODO: + * + * @return TODO + */ function backtrack($backtrack) { $this->offset = $backtrack; } + + /** + * TODO: + * + * @return TODO + */ function expect($token) { if ($this->offset >= count($this->tokens)) { return false; @@ -45,6 +101,12 @@ class Parser { } return $currentTok['match']; } + + /** + * TODO: + * + * @return TODO + */ function parseBoolTerm() { $bt = $this->getBacktrack(); $left = $this->parseUnaryBool(); @@ -65,6 +127,12 @@ class Parser { 'args' => array($left, $right), ); } + + /** + * TODO: + * + * @return TODO + */ function parseUnaryBool() { $bt = $this->getBacktrack(); $op = $this->expect("not"); @@ -83,6 +151,12 @@ class Parser { 'args' => array($arg), ); } + + /** + * TODO: + * + * @return TODO + */ function parseBoolComp() { $bt = $this->getBacktrack(); $left = $this->parseNumTerm(); @@ -103,6 +177,12 @@ class Parser { 'args' => array($left, $right), ); } + + /** + * TODO: + * + * @return TODO + */ function parseNumTerm() { $bt = $this->getBacktrack(); $left = $this->parseNumFactor(); @@ -123,6 +203,12 @@ class Parser { 'args' => array($left, $right), ); } + + /** + * TODO: + * + * @return TODO + */ function parseNumFactor() { $bt = $this->getBacktrack(); $left = $this->parseNumPower(); @@ -143,6 +229,12 @@ class Parser { 'args' => array($left, $right), ); } + + /** + * TODO: + * + * @return TODO + */ function parseNumPower() { $bt = $this->getBacktrack(); $left = $this->parseUnaryFact(); @@ -162,6 +254,12 @@ class Parser { 'args' => array($left, $right), ); } + + /** + * TODO: + * + * @return TODO + */ function parseUnaryFact() { $arg = $this->parseUnaryPercent(); $op = $this->expect("!"); @@ -175,6 +273,12 @@ class Parser { 'args' => array($arg), ); } + + /** + * TODO: + * + * @return TODO + */ function parseUnaryPercent() { $arg = $this->parseUnaryMinus(); $op = $this->expect("%"); @@ -188,6 +292,12 @@ class Parser { 'args' => array($arg), ); } + + /** + * TODO: + * + * @return TODO + */ function parseUnaryMinus() { $op = $this->expect("-"); if ($op === false) { @@ -201,6 +311,12 @@ class Parser { 'args' => array($arg), ); } + + /** + * TODO: + * + * @return TODO + */ function parseTerminal() { return ( $this->parseFuncCall() ?: @@ -211,6 +327,12 @@ class Parser { $this->parseConstant() ); } + + /** + * TODO: + * + * @return TODO + */ function parseNested() { $bt = $this->getBacktrack(); if ($this->expect("(") === false) { @@ -229,6 +351,12 @@ class Parser { $this->next(); return $expr; } + + /** + * TODO: + * + * @return TODO + */ function parseNumber() { $num = $this->expect("NUMBER"); if ($num === false) { @@ -240,6 +368,12 @@ class Parser { 'args' => array($num), ); } + + /** + * TODO: + * + * @return TODO + */ function parseString() { $str = $this->expect("STRING"); if ($str === false) { @@ -259,6 +393,12 @@ class Parser { 'args' => array($str), ); } + + /** + * TODO: + * + * @return TODO + */ function parseConstant() { $filt = array_values(array_filter(["false", "true", "null", "E", "PI"], array($this, 'expect'))); if (count($filt) === 0) { @@ -270,6 +410,12 @@ class Parser { 'args' => array($filt[0]), ); } + + /** + * TODO: + * + * @return TODO + */ function parseFuncCall() { $bt = $this->getBacktrack(); $func = $this->expect("VARIABLE"); @@ -293,6 +439,12 @@ class Parser { 'args' => array($func, $args), ); } + + /** + * TODO: + * + * @return TODO + */ function parseArguments() { $bt = $this->getBacktrack(); $arg = $this->parseBoolTerm(); @@ -310,6 +462,12 @@ class Parser { } return array_merge([$arg], $nextArgs);; } + + /** + * TODO: + * + * @return TODO + */ function parseVariable() { $sym = $this->parseVarSymbol(); if ($sym === false) { @@ -336,6 +494,12 @@ class Parser { 'args' => array($var, $accessors), ); } + + /** + * TODO: + * + * @return TODO + */ function parseVarSymbol() { $bt = $this->getBacktrack(); if ($this->expect("[") === false) { @@ -359,6 +523,12 @@ class Parser { } return array('var' => $var, 'num' => $nest); } + + /** + * TODO: + * + * @return TODO + */ function parseNestedVar() { $bt = $this->getBacktrack(); if ($this->expect("(") === false) { From 64f4a21b97b8a892234a3c5548693dc03c95a346 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 13:14:23 -0400 Subject: [PATCH 206/214] Fix linting issues --- php/libraries/Parser.class.inc | 321 +++++++++++++++++++++------------ 1 file changed, 204 insertions(+), 117 deletions(-) diff --git a/php/libraries/Parser.class.inc b/php/libraries/Parser.class.inc index a95a6b5054e..412385ab57b 100644 --- a/php/libraries/Parser.class.inc +++ b/php/libraries/Parser.class.inc @@ -25,19 +25,22 @@ use \Exception; */ class Parser { - private $expression; - private $tokens; - private $offset; + private $_expression; + private $_tokens; + private $_offset; /** * TODO: * + * @param expression $expression TODO + * * @return TODO */ - function __construct($expression) { - $this->expression = $expression; - $this->tokens = Lexer::lex($expression); - $this->offset = 0; + function __construct($expression) + { + $this->_expression = $expression; + $this->_tokens = Lexer::lex($expression); + $this->_offset = 0; } /** @@ -45,13 +48,17 @@ class Parser * * @return TODO */ - function parse() { + function parse() + { $ast = $this->parseBoolTerm(); if ($ast === false) { throw new Exception("Cannot parse expression."); } - if ($this->offset < count($this->tokens)) { - throw new Exception("Unexpected token(s) after: " . substr($this->expression, $this->offset)); + if ($this->_offset < count($this->_tokens)) { + throw new Exception( + "Unexpected token(s) after: " . + substr($this->_expression, $this->_offset) + ); } return $ast; } @@ -61,10 +68,11 @@ class Parser * * @return TODO */ - function next() { - $this->offset += 1; - if ($this->offset > count($this->tokens)) { - throw new Exception("Invalid expression ending: " . $this->expression); + function next() + { + $this->_offset += 1; + if ($this->_offset > count($this->_tokens)) { + throw new Exception("Invalid expression ending: " . $this->_expression); } } @@ -73,29 +81,36 @@ class Parser * * @return TODO */ - function getBacktrack() { - return $this->offset; + function getBacktrack() + { + return $this->_offset; } /** * TODO: * + * @param backtrack $backtrack TODO + * * @return TODO */ - function backtrack($backtrack) { - $this->offset = $backtrack; + function backtrack($backtrack) + { + $this->_offset = $backtrack; } /** * TODO: * + * @param token $token TODO + * * @return TODO */ - function expect($token) { - if ($this->offset >= count($this->tokens)) { + function expect($token) + { + if ($this->_offset >= count($this->_tokens)) { return false; } - $currentTok = $this->tokens[$this->offset]; + $currentTok = $this->_tokens[$this->_offset]; if ($currentTok['token'] !== $token) { return false; } @@ -107,25 +122,29 @@ class Parser * * @return TODO */ - function parseBoolTerm() { - $bt = $this->getBacktrack(); + function parseBoolTerm() + { + $bt = $this->getBacktrack(); $left = $this->parseUnaryBool(); - $filt = array_values(array_filter(["and","or"], array($this, 'expect'))); + $filt = array_values(array_filter(["and", "or"], array($this, 'expect'))); if (count($filt) === 0) { return $left; } $this->next(); - $op = $filt[0]; + $op = $filt[0]; $right = $this->parseBoolTerm(); if ($right === false) { $this->backtrack($bt); return false; } return array( - 'tag' => 'BinaryOp', - 'op' => $op, - 'args' => array($left, $right), - ); + 'tag' => 'BinaryOp', + 'op' => $op, + 'args' => array( + $left, + $right, + ), + ); } /** @@ -133,7 +152,8 @@ class Parser * * @return TODO */ - function parseUnaryBool() { + function parseUnaryBool() + { $bt = $this->getBacktrack(); $op = $this->expect("not"); if ($op === false) { @@ -146,10 +166,10 @@ class Parser return false; } return array( - 'tag' => 'UnaryOp', - 'op' => $op, - 'args' => array($arg), - ); + 'tag' => 'UnaryOp', + 'op' => $op, + 'args' => array($arg), + ); } /** @@ -157,25 +177,44 @@ class Parser * * @return TODO */ - function parseBoolComp() { - $bt = $this->getBacktrack(); + function parseBoolComp() + { + $bt = $this->getBacktrack(); $left = $this->parseNumTerm(); - $filt = array_values(array_filter(["=", "<", ">", "<>", "<=", ">="], array($this, 'expect'))); + $filt = array_values( + array_filter( + [ + "=", + "<", + ">", + "<>", + "<=", + ">=", + ], + array( + $this, + 'expect', + ) + ) + ); if (count($filt) === 0) { return $left; } $this->next(); - $op = $filt[0]; + $op = $filt[0]; $right = $this->parseBoolComp(); if ($right === false) { $this->backtrack($bt); return false; } return array( - 'tag' => 'BinaryOp', - 'op' => $op, - 'args' => array($left, $right), - ); + 'tag' => 'BinaryOp', + 'op' => $op, + 'args' => array( + $left, + $right, + ), + ); } /** @@ -183,25 +222,29 @@ class Parser * * @return TODO */ - function parseNumTerm() { - $bt = $this->getBacktrack(); + function parseNumTerm() + { + $bt = $this->getBacktrack(); $left = $this->parseNumFactor(); $filt = array_values(array_filter(["+", "-"], array($this, 'expect'))); if (count($filt) === 0) { return $left; } $this->next(); - $op = $filt[0]; + $op = $filt[0]; $right = $this->parseNumTerm(); if ($right === false) { $this->backtrack($bt); return false; } return array( - 'tag' => 'BinaryOp', - 'op' => $op, - 'args' => array($left, $right), - ); + 'tag' => 'BinaryOp', + 'op' => $op, + 'args' => array( + $left, + $right, + ), + ); } /** @@ -209,25 +252,29 @@ class Parser * * @return TODO */ - function parseNumFactor() { - $bt = $this->getBacktrack(); + function parseNumFactor() + { + $bt = $this->getBacktrack(); $left = $this->parseNumPower(); $filt = array_values(array_filter(["*", "/"], array($this, 'expect'))); if (count($filt) === 0) { return $left; } $this->next(); - $op = $filt[0]; + $op = $filt[0]; $right = $this->parseNumFactor(); if ($right === false) { $this->backtrack($bt); return false; } return array( - 'tag' => 'BinaryOp', - 'op' => $op, - 'args' => array($left, $right), - ); + 'tag' => 'BinaryOp', + 'op' => $op, + 'args' => array( + $left, + $right, + ), + ); } /** @@ -235,13 +282,14 @@ class Parser * * @return TODO */ - function parseNumPower() { - $bt = $this->getBacktrack(); + function parseNumPower() + { + $bt = $this->getBacktrack(); $left = $this->parseUnaryFact(); - $op = $this->expect("^"); + $op = $this->expect("^"); if ($op === false) { return $left; - } + } $this->next(); $right = $this->parseNumPower(); if ($right === false) { @@ -249,10 +297,13 @@ class Parser return false; } return array( - 'tag' => 'BinaryOp', - 'op' => $op, - 'args' => array($left, $right), - ); + 'tag' => 'BinaryOp', + 'op' => $op, + 'args' => array( + $left, + $right, + ), + ); } /** @@ -260,18 +311,19 @@ class Parser * * @return TODO */ - function parseUnaryFact() { + function parseUnaryFact() + { $arg = $this->parseUnaryPercent(); - $op = $this->expect("!"); + $op = $this->expect("!"); if ($op === false) { return $arg; } $this->next(); return array( - 'tag' => 'UnaryOp', - 'op' => $op, - 'args' => array($arg), - ); + 'tag' => 'UnaryOp', + 'op' => $op, + 'args' => array($arg), + ); } /** @@ -279,18 +331,19 @@ class Parser * * @return TODO */ - function parseUnaryPercent() { + function parseUnaryPercent() + { $arg = $this->parseUnaryMinus(); - $op = $this->expect("%"); + $op = $this->expect("%"); if ($op === false) { return $arg; } $this->next(); return array( - 'tag' => 'UnaryOp', - 'op' => $op, - 'args' => array($arg), - ); + 'tag' => 'UnaryOp', + 'op' => $op, + 'args' => array($arg), + ); } /** @@ -298,7 +351,8 @@ class Parser * * @return TODO */ - function parseUnaryMinus() { + function parseUnaryMinus() + { $op = $this->expect("-"); if ($op === false) { return $this->parseTerminal(); @@ -306,10 +360,10 @@ class Parser $this->next(); $arg = $this->parseTerminal(); return array( - 'tag' => 'UnaryOp', - 'op' => $op, - 'args' => array($arg), - ); + 'tag' => 'UnaryOp', + 'op' => $op, + 'args' => array($arg), + ); } /** @@ -317,7 +371,8 @@ class Parser * * @return TODO */ - function parseTerminal() { + function parseTerminal() + { return ( $this->parseFuncCall() ?: $this->parseVariable() ?: @@ -333,7 +388,8 @@ class Parser * * @return TODO */ - function parseNested() { + function parseNested() + { $bt = $this->getBacktrack(); if ($this->expect("(") === false) { return false; @@ -357,16 +413,17 @@ class Parser * * @return TODO */ - function parseNumber() { + function parseNumber() + { $num = $this->expect("NUMBER"); if ($num === false) { return false; } $this->next(); return array( - 'tag' => 'Number', - 'args' => array($num), - ); + 'tag' => 'Number', + 'args' => array($num), + ); } /** @@ -374,7 +431,8 @@ class Parser * * @return TODO */ - function parseString() { + function parseString() + { $str = $this->expect("STRING"); if ($str === false) { $str =$this->expect("ESTRING"); @@ -383,15 +441,15 @@ class Parser } $this->next(); return array( - 'tag' => 'EString', - 'args' => array($str), - ); + 'tag' => 'EString', + 'args' => array($str), + ); } $this->next(); return array( - 'tag' => 'String', - 'args' => array($str), - ); + 'tag' => 'String', + 'args' => array($str), + ); } /** @@ -399,16 +457,31 @@ class Parser * * @return TODO */ - function parseConstant() { - $filt = array_values(array_filter(["false", "true", "null", "E", "PI"], array($this, 'expect'))); + function parseConstant() + { + $filt = array_values( + array_filter( + [ + "false", + "true", + "null", + "E", + "PI", + ], + array( + $this, + 'expect', + ) + ) + ); if (count($filt) === 0) { return false; } $this->next(); return array( - 'tag' => 'Constant', - 'args' => array($filt[0]), - ); + 'tag' => 'Constant', + 'args' => array($filt[0]), + ); } /** @@ -416,8 +489,9 @@ class Parser * * @return TODO */ - function parseFuncCall() { - $bt = $this->getBacktrack(); + function parseFuncCall() + { + $bt = $this->getBacktrack(); $func = $this->expect("VARIABLE"); if ($func === false) { return false; @@ -435,9 +509,12 @@ class Parser } $this->next(); return array( - 'tag' => 'FuncApplication', - 'args' => array($func, $args), - ); + 'tag' => 'FuncApplication', + 'args' => array( + $func, + $args, + ), + ); } /** @@ -445,8 +522,9 @@ class Parser * * @return TODO */ - function parseArguments() { - $bt = $this->getBacktrack(); + function parseArguments() + { + $bt = $this->getBacktrack(); $arg = $this->parseBoolTerm(); if ($arg === false) { return []; @@ -468,12 +546,13 @@ class Parser * * @return TODO */ - function parseVariable() { + function parseVariable() + { $sym = $this->parseVarSymbol(); if ($sym === false) { return false; } - $var = $sym['var']; + $var = $sym['var']; $accessors = isset($sym['num']) ? [$sym['num']] : []; while ($access = $this->parseVarSymbol()) { if (isset($access['var'])) { @@ -485,14 +564,17 @@ class Parser }; if (count($accessors) === 0) { return array( - 'tag' => 'Variable', - 'args' => array($var), - ); + 'tag' => 'Variable', + 'args' => array($var), + ); } return array( - 'tag' => 'NestedVariable', - 'args' => array($var, $accessors), - ); + 'tag' => 'NestedVariable', + 'args' => array( + $var, + $accessors, + ), + ); } /** @@ -500,7 +582,8 @@ class Parser * * @return TODO */ - function parseVarSymbol() { + function parseVarSymbol() + { $bt = $this->getBacktrack(); if ($this->expect("[") === false) { return false; @@ -521,7 +604,10 @@ class Parser if ($nest === false) { return array('var' => $var); } - return array('var' => $var, 'num' => $nest); + return array( + 'var' => $var, + 'num' => $nest, + ); } /** @@ -529,7 +615,8 @@ class Parser * * @return TODO */ - function parseNestedVar() { + function parseNestedVar() + { $bt = $this->getBacktrack(); if ($this->expect("(") === false) { $this->backtrack($bt); From f1797b4250dce41fd44ca41e6b8fa11126e80012 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 13:22:38 -0400 Subject: [PATCH 207/214] Fix linting issues --- php/libraries/Functions.php | 505 +++++++++++++++++++++++------------- 1 file changed, 320 insertions(+), 185 deletions(-) diff --git a/php/libraries/Functions.php b/php/libraries/Functions.php index 78eb2340414..194f8aaa78b 100644 --- a/php/libraries/Functions.php +++ b/php/libraries/Functions.php @@ -1,206 +1,341 @@ + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ namespace LORIS\LorisScript; -function factHelper1 ($n) { - if ($n == 0) return 1; +/** + * TODO: + * + * @param number $n TODO + * + * @return number + */ +function factHelper1($n) +{ + if ($n == 0) { + return 1; + } return factHelper1($n-1)*$n; } -function factHelper2 ($n) { - if ($n == 0.5) return sqrt(M_PI)/2; +/** + * TODO: + * + * @param number $n TODO + * + * @return number + */ +function factHelper2($n) +{ + if ($n == 0.5) { + return sqrt(M_PI)/2; + } return factHelper2($n-1)*$n; } -function getFunctions() { +/** + * TODO: + * + * @return array + */ +function getFunctions() +{ return array( - '_eq' => function ($a, $b) { - return $a == $b; - }, - '_neq' => function ($a, $b) { - return $a != $b; - }, - '_gt' => function ($a, $b) { - return $a > $b; - }, - '_lt' => function ($a, $b) { - return $a < $b; - }, - '_geq' => function ($a, $b) { - return $a >= $b; - }, - '_leq' => function ($a, $b) { - return $a <= $b; - }, - '_add' => function ($a, $b) { - return $a + $b; - }, - '_sub' => function ($a, $b) { - return $a - $b; - }, - '_mul' => function ($a, $b) { - return $a * $b; - }, - '_div' => function ($a, $b) { - return $a / $b; - }, - '_pow' => function ($a, $b) { - return $a ** $b; - }, - '_mod' => function ($a, $b) { - return $a % $b; - }, - '_negate' => function ($a) { - return -$a; - }, - '_per' => function ($a) { - return $a / 100; - }, - '_and' => function ($a, $b) { - return $a and $b; - }, - '_or' => function ($a, $b) { - return $a or $b; - }, - '_not' => function ($a) { - return !$a; - }, - '_fact' => function ($a) { - if ($a >=0 && $a%1 == 0) { - return factHelper1($a); - } else if ($a >=0 && $a%1 == 0.5) { - return factHelper2($a); - } else { - throw "Factorial for a number not divisible by 0.5 or greater than 0 is not supported."; - } - }, - '_factHelper1' => function ($n) { - if ($n == 0) return 1; - return rec($n-1)*$n; - }, - '_factHelper2' => function ($n) { - if ($n == 0.5) return sqrt(M_PI)/2; - return rec($n-1)*$n; - }, - '_isNan' => function ($a) { - if (is_numeric($a)) { - return false; - } else return true; - }, - '_round' => function ($n, $places = 0) { - $shift = 10 ** $places; - return round($n * $shift) / $shift; - }, - '_roundup' => function ($n, $places = 0) { - $shift = 10 ** $places; - return ceil($n * $shift) / $shift; - }, - '_rounddown' => function ($n, $places = 0) { - $shift = 10 ** $places; - return floor($n * $shift) / $shift; - }, - '_sqrt' => function ($a) { - return sqrt($a); - }, - '_abs' => function ($a) { - return abs($a); - }, - '_min' => function () { - $args = func_get_args(); - return min($args); - }, - '_max' => function () { - $args = func_get_args(); - return max($args); - }, - '_mean' => function () { - $args = func_get_args(); - if (count($args) == 0) throw "Cannot find mean of 0 arguments"; - return array_reduce($args, function($a, $b){ return $a+$b; }, 0) / count($args); - }, - '_median' => function () { - $args = func_get_args(); - if (count($args) == 0) throw "Cannot find median of 0 arguments"; - $cpy = array_map(function($x){return $x;}, $args); - $mid = count($cpy) / 2; - sort($cpy); - if(count($cpy) % 2 == 0) { - return ($cpy[$mid] + $cpy[$mid - 1]) / 2; - } else { - return $cpy[$mid]; - } - }, - '_sum' => function () { - $args = func_get_args(); - return array_reduce($args, function($a, $b){ return $a+$b; }, 0); - }, - '_product' => function () { - $args = func_get_args(); - return array_reduce($args, function($a, $b){ return $a*$b; }, 1); - }, - '_variance' => function () { - $args = func_get_args(); - if (count($args) == 0) return 0; - $mean = array_reduce($args, function($a, $b){ return $a+$b; }, 0) / count($args); - $sqDiffs = array_map(function($value) use ($mean) { return ($value-$mean)**2; }, $args); - return array_reduce($sqDiffs, function($a, $b){ return $a+$b; }, 0) / count($args); - }, - '_stdev' => function () { - $args = func_get_args(); - if (count($args) == 0) return 0; - $mean = array_reduce($args, function($a, $b){ return $a+$b; }, 0) / count($args); - $sqDiffs = array_map(function($value) use ($mean) { return ($value-$mean)**2; }, $args); - $variance = array_reduce($sqDiffs, function($a, $b){ return $a+$b; }, 0) / count($args); - return sqrt($variance); - }, - '_datediff' => function ($date1, $date2, $units, $returnSigned=false) { - $dt1 = new \DateTime($date1); - $dt2 = new \DateTime($date2); - $interval = date_diff($dt1, $dt2, !$returnSigned); - $res; - if ($units === 'y' || $units === 'Y') { - $res = ($interval->format("%y")+$interval->format("%m")/12+$interval->format("%d")/365); - } else if ($units === 'm' || $units === 'M') { - $res = ($interval->format("%y")*12+$interval->format("%m")+$interval->format("%d")/30.44); - } else if ($units === 'd' || $units === 'D') { - $res = ($interval->format("%y")*365+$interval->format("%m")*30.44+$interval->format("%d")); - } else { - return 0; - } - if (!$returnSigned) { - $res = abs($res); - } - return $res; - } - ); + '_eq' => function ($a, $b) { + return $a == $b; + }, + '_neq' => function ($a, $b) { + return $a != $b; + }, + '_gt' => function ($a, $b) { + return $a > $b; + }, + '_lt' => function ($a, $b) { + return $a < $b; + }, + '_geq' => function ($a, $b) { + return $a >= $b; + }, + '_leq' => function ($a, $b) { + return $a <= $b; + }, + '_add' => function ($a, $b) { + return $a + $b; + }, + '_sub' => function ($a, $b) { + return $a - $b; + }, + '_mul' => function ($a, $b) { + return $a * $b; + }, + '_div' => function ($a, $b) { + return $a / $b; + }, + '_pow' => function ($a, $b) { + return $a ** $b; + }, + '_mod' => function ($a, $b) { + return $a % $b; + }, + '_negate' => function ($a) { + return -$a; + }, + '_per' => function ($a) { + return $a / 100; + }, + '_and' => function ($a, $b) { + return $a and $b; + }, + '_or' => function ($a, $b) { + return $a or $b; + }, + '_not' => function ($a) { + return !$a; + }, + '_fact' => function ($a) { + if ($a >=0 && $a%1 == 0) { + return factHelper1($a); + } else if ($a >=0 && $a%1 == 0.5) { + return factHelper2($a); + } else { + throw "Factorial for a number not divisible by 0.5 or + greater than 0 is not supported."; + } + }, + '_factHelper1' => function ($n) { + if ($n == 0) { + return 1; + } + return rec($n-1)*$n; + }, + '_factHelper2' => function ($n) { + if ($n == 0.5) { + return sqrt(M_PI)/2; + } + return rec($n-1)*$n; + }, + '_isNan' => function ($a) { + if (is_numeric($a)) { + return false; + } else { + return true; + } + }, + '_round' => function ($n, $places = 0) { + $shift = 10 ** $places; + return round($n * $shift) / $shift; + }, + '_roundup' => function ($n, $places = 0) { + $shift = 10 ** $places; + return ceil($n * $shift) / $shift; + }, + '_rounddown' => function ($n, $places = 0) { + $shift = 10 ** $places; + return floor($n * $shift) / $shift; + }, + '_sqrt' => function ($a) { + return sqrt($a); + }, + '_abs' => function ($a) { + return abs($a); + }, + '_min' => function () { + $args = func_get_args(); + return min($args); + }, + '_max' => function () { + $args = func_get_args(); + return max($args); + }, + '_mean' => function () { + $args = func_get_args(); + if (count($args) == 0) { + throw "Cannot find mean of 0 arguments"; + } + return array_reduce( + $args, + function ($a, $b) { + return $a+$b; + }, + 0 + ) / count($args); + }, + '_median' => function () { + $args = func_get_args(); + if (count($args) == 0) { + throw "Cannot find median of 0 arguments"; + } + $cpy = array_map( + function ($x) { + return $x; + }, + $args + ); + $mid = count($cpy) / 2; + sort($cpy); + if (count($cpy) % 2 == 0) { + return ($cpy[$mid] + $cpy[$mid - 1]) / 2; + } else { + return $cpy[$mid]; + } + }, + '_sum' => function () { + $args = func_get_args(); + return array_reduce( + $args, + function ($a, $b) { + return $a+$b; + }, + 0 + ); + }, + '_product' => function () { + $args = func_get_args(); + return array_reduce( + $args, + function ($a, $b) { + return $a*$b; + }, + 1 + ); + }, + '_variance' => function () { + $args = func_get_args(); + if (count($args) == 0) { + return 0; + } + $mean = array_reduce( + $args, + function ($a, $b) { + return $a+$b; + }, + 0 + ) / count($args); + $sqDiffs = array_map( + function ($value) use ($mean) { + return ($value-$mean)**2; + }, + $args + ); + return array_reduce( + $sqDiffs, + function ($a, $b) { + return $a+$b; + }, + 0 + ) / count($args); + }, + '_stdev' => function () { + $args = func_get_args(); + if (count($args) == 0) { + return 0; + } + $mean = array_reduce( + $args, + function ($a, $b) { + return $a+$b; + }, + 0 + ) / count($args); + $sqDiffs = array_map( + function ($value) use ($mean) { + return ($value-$mean)**2; + }, + $args + ); + $variance = array_reduce( + $sqDiffs, + function ($a, $b) { + return $a+$b; + }, + 0 + ) / count($args); + return sqrt($variance); + }, + '_datediff' => function ($date1, $date2, + $units, $returnSigned=false + ) { + $dt1 = new \DateTime($date1); + $dt2 = new \DateTime($date2); + $interval = date_diff($dt1, $dt2, !$returnSigned); + $res; + if ($units === 'y' || $units === 'Y') { + $res = ( + $interval->format("%y") + + $interval->format("%m")/12 + + $interval->format("%d")/365 + ); + } else if ($units === 'm' || $units === 'M') { + $res = ( + $interval->format("%y")*12 + + $interval->format("%m") + + $interval->format("%d")/30.44 + ); + } else if ($units === 'd' || $units === 'D') { + $res = ( + $interval->format("%y")*365 + + $interval->format("%m")*30.44 + + $interval->format("%d") + ); + } else { + return 0; + } + if (!$returnSigned) { + $res = abs($res); + } + return $res; + }, + ); } -function getBinary() { +/** + * TODO: + * + * @return array + */ +function getBinary() +{ $FUNCTIONS = getFunctions(); return array( - '_+' => $FUNCTIONS['_add'], - '_-' => $FUNCTIONS['_sub'], - '_=' => $FUNCTIONS['_eq'], - '_<>' => $FUNCTIONS['_neq'], - '_>' => $FUNCTIONS['_gt'], - '_<' => $FUNCTIONS['_lt'], - '_>=' => $FUNCTIONS['_geq'], - '_<=' => $FUNCTIONS['_leq'], - '_*' => $FUNCTIONS['_mul'], - '_/' => $FUNCTIONS['_div'], - '_^' => $FUNCTIONS['_pow'], - '_and' => $FUNCTIONS['_and'], - '_or' => $FUNCTIONS['_or'] - ); + '_+' => $FUNCTIONS['_add'], + '_-' => $FUNCTIONS['_sub'], + '_=' => $FUNCTIONS['_eq'], + '_<>' => $FUNCTIONS['_neq'], + '_>' => $FUNCTIONS['_gt'], + '_<' => $FUNCTIONS['_lt'], + '_>=' => $FUNCTIONS['_geq'], + '_<=' => $FUNCTIONS['_leq'], + '_*' => $FUNCTIONS['_mul'], + '_/' => $FUNCTIONS['_div'], + '_^' => $FUNCTIONS['_pow'], + '_and' => $FUNCTIONS['_and'], + '_or' => $FUNCTIONS['_or'], + ); } -function getUnary() { +/** + * TODO: + * + * @return array + */ +function getUnary() +{ $FUNCTIONS = getFunctions(); return array( - '_!' => $FUNCTIONS['_fact'], - '_%' => $FUNCTIONS['_per'], - '_not' => $FUNCTIONS['_not'], - '_-' => $FUNCTIONS['_negate'], - ); + '_!' => $FUNCTIONS['_fact'], + '_%' => $FUNCTIONS['_per'], + '_not' => $FUNCTIONS['_not'], + '_-' => $FUNCTIONS['_negate'], + ); } ?> From 13800a52afd44e0e02c39a038ce2153b7e227233 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 13:24:02 -0400 Subject: [PATCH 208/214] Fix linting issues --- php/libraries/NDB_BVL_Instrument.class.inc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/php/libraries/NDB_BVL_Instrument.class.inc b/php/libraries/NDB_BVL_Instrument.class.inc index af13556b514..a4cf47191b8 100644 --- a/php/libraries/NDB_BVL_Instrument.class.inc +++ b/php/libraries/NDB_BVL_Instrument.class.inc @@ -185,14 +185,15 @@ class NDB_BVL_Instrument extends NDB_Page $obj = new \Loris\Behavioural\NDB_BVL_Instrument_JSON(); $obj->setup($commentID, $page); - $lang = $_REQUEST["lang"] ? $_REQUEST["lang"] : "en-ca"; + $lang = $_REQUEST["lang"] ? $_REQUEST["lang"] : "en-ca"; $filename = $base."project/instruments/$instrument.json"; $obj->loadInstrumentFile($filename); } else if (file_exists($base."project/instruments/$instrument.linst")) { include_once 'NDB_BVL_Instrument_LINST.class.inc'; $obj = new \Loris\Behavioural\NDB_BVL_Instrument_LINST(); - // if a script is loading this form without a commentID, display all fields + // if a script is loading this form without a commentID, + // display all fields if (!isset($commentID)) { $obj->displayAllFields = true; } From 7f465021da5c67609f5faf9e3b2a4f0b49cd7f8c Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 13:44:48 -0400 Subject: [PATCH 209/214] Fix linting issues --- .../NDB_BVL_Instrument_JSON.class.inc | 444 +++++++++++++----- 1 file changed, 325 insertions(+), 119 deletions(-) diff --git a/php/libraries/NDB_BVL_Instrument_JSON.class.inc b/php/libraries/NDB_BVL_Instrument_JSON.class.inc index 8440d35f060..e90aa863f64 100644 --- a/php/libraries/NDB_BVL_Instrument_JSON.class.inc +++ b/php/libraries/NDB_BVL_Instrument_JSON.class.inc @@ -35,45 +35,64 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument * * @return none */ - function setup($commentID = NULL, $page = NULL) + function setup($commentID = null, $page = null) { - $this->commentID = $commentID; - $this->page = $page; + $this->commentID = $commentID; + $this->page = $page; parent::setup($commentID, $page); } /** * Returns candidate-related context variables to be accessed during runtime * Allows access to: date of birth, age in months (calculated) - * + * * @return array containing context variables */ - function _getContext() { + function _getContext() + { $timepoint = \TimePoint::singleton($this->getSessionID()); - $candID = $timepoint->getCandID(); + $candID = $timepoint->getCandID(); $candidate = \Candidate::singleton($candID); - $dob = $candidate->getCandidateDoB(); - $dobDT = new \DateTime($dob); - $curDate = new \DateTime("now"); - $interval = date_diff($curDate, $dobDT, true); - $ageM = $this->calculateAgeMonths(array('year' => $interval->format("%y"), - 'mon' => $interval->format("%m"), - 'day' => $interval->format("%d"))); - //$ageTEST = $this->getCandidateAge($curDate); //TODO replace the hacky age function with this + $dob = $candidate->getCandidateDoB(); + $dobDT = new \DateTime($dob); + $curDate = new \DateTime("now"); + $interval = date_diff($curDate, $dobDT, true); + $ageM = $this->calculateAgeMonths( + array( + 'year' => $interval->format("%y"), + 'mon' => $interval->format("%m"), + 'day' => $interval->format("%d"), + ) + ); + //TODO replace the hacky age function with this + //$ageTEST = $this->getCandidateAge($curDate); $gender = $candidate->getCandidateGender(); return array( - "age_mths" => $ageM, - "dob" => $dob, - "gender" => $gender - ); + "age_mths" => $ageM, + "dob" => $dob, + "gender" => $gender, + ); } - function _getLang() { + /** + * Returns the lang to be used + * + * @return string + */ + function _getLang() + { return "en-CA"; } - - function _getInstrumentData($db) { + /** + * Returns the instrument data + * + * @param db $db DB connection + * + * @return array + */ + function _getInstrumentData($db) + { $result = $db->pselect( "SELECT * FROM $this->table WHERE CommentID=:CID", array('CID' => $this->getCommentID()) @@ -82,9 +101,12 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument } /** + * Loads instrument JSON file + * * @param string $filename The filename to be loaded, or a base64 encoded * string of a .json file to be interpreted. - * @param boolean $base64 (NOT IMPLEMENTED) If true, read the filename as a base64 encoded + * @param boolean $base64 (NOT IMPLEMENTED) If true, read the filename + * as a base64 encoded * string of the file content, used for preview * when no file has yet been saved to the * instruments/ directory @@ -97,59 +119,129 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument $this->InstrumentType = 'JSON'; $json = file_get_contents($filename); $this->instrumentJSON = $json; - $this->instrument = json_decode($json, true); - $this->instrument["Elements"] = self::inlineCalcFormulas($this->instrument["Elements"]); - $this->table = $this->instrument['Meta']['ShortName']; + $this->instrument = json_decode($json, true); + $this->instrument["Elements"] = self::inlineCalcFormulas( + $this->instrument["Elements"] + ); + $this->table = $this->instrument['Meta']['ShortName']; $this->testName = $this->table; return $json; } } - function setOptions($options = array()) { + /** + * Set the options for this instrument + * + * @param array $options Options + * + * @return none + */ + function setOptions($options = array()) + { $this->options = $options; } - static function isDisplayed($element, $values, $context, $surveyMode) { - if ( - ($element["Hidden"]) || - ($surveyMode && $element["HiddenSurvey"]) || - ($element.DisplayIf === false) + /** + * Set the options for this instrument + * + * @param array $element Element + * @param array $values Values + * @param array $context Context + * @param boolean $surveyMode Boolean indicating whether in Survey Mode or not + * + * @return array + */ + static function isDisplayed($element, $values, $context, $surveyMode) + { + if (($element["Hidden"]) + || ($surveyMode && $element["HiddenSurvey"]) + || ($element.DisplayIf === false) ) { return false; } - if ($element["DisplayIf" === ""]) return true; + if ($element["DisplayIf" === ""]) { + return true; + } $displayIf = $element["DisplayIf"]; $scope = array_merge($values, array("context" => $context)); - $res = Evaluator::evaluate($displayIf, $scope); + $res = Evaluator::evaluate($displayIf, $scope); return $res; } - static function filterElements($elements, $values, $context, $surveyMode) { - return array_filter($elements, function($element) use ($values, $context, $surveyMode) { - return self::isDisplayed($element, $values, $context, $surveyMode); - }); + /** + * Returns only the elements that should be visible + * + * @param array $elements Elements + * @param array $values Values + * @param array $context Context + * @param boolean $surveyMode Boolean indicating whether in Survey Mode or not + * + * @return array + */ + static function filterElements($elements, $values, $context, $surveyMode) + { + return array_filter( + $elements, + function ($element) use ($values, $context, $surveyMode) { + return self::isDisplayed($element, $values, $context, $surveyMode); + } + ); } - static function isRequired($element, $values, $context) { - $inputTypes = ['select', 'date', 'radio', 'text', 'score', 'checkbox']; - - if (!in_array($element["Type"], $inputTypes)) return false; + /** + * Determines whether an element should be required + * + * @param array $element Element + * @param array $values Values + * @param array $context Context + * + * @return array + */ + static function isRequired($element, $values, $context) + { + $inputTypes = [ + 'select', + 'date', + 'radio', + 'text', + 'score', + 'checkbox', + ]; + + if (!in_array($element["Type"], $inputTypes)) { + return false; + } $requireResponse = $element["Options"]["RequireResponse"]; - if (!isset($requireResponse)) return false; + if (!isset($requireResponse)) { + return false; + } - if (is_bool($requireResponse)) return $requireResponse; + if (is_bool($requireResponse)) { + return $requireResponse; + } - return Evaluator::evaluate($requireResponse, array_merge($values, array("context" => $context))); + return Evaluator::evaluate( + $requireResponse, + array_merge($values, array("context" => $context)) + ); } - function incompleteExists($values) { + /** + * Checks if submitted values are missing any required fields + * + * @param array $values Values + * + * @return boolean + */ + function incompleteExists($values) + { $incompleteExists = false; - $context = $this->_getContext(); - $visibleElements = self::filterElements( + $context = $this->_getContext(); + $visibleElements = self::filterElements( $this->instrument["Elements"], $values, $context, @@ -160,20 +252,37 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument if (isset($values[$element["Name"]])) { continue; } else { - $isRequired = self::isRequired($element, $values, $this->_getContext()); - if ($isRequired) { $incompleteExists = true; } + $isRequired = self::isRequired( + $element, + $values, + $this->_getContext() + ); + if ($isRequired) { + $incompleteExists = true; + } } } return $incompleteExists; } - function calculateFields($values) { + /** + * Calculates the values of score elements + * + * @param array $values Values + * + * @return array + */ + function calculateFields($values) + { $calculatedValues = array(); foreach ($this->instrument["Elements"] as &$element) { if ($element["Type"] == 'score') { - $calculatedValues[$element["Name"]] = Evaluator::evaluate($element["Formula"], array_merge($values, array("context" => $this->_getContext()))); + $calculatedValues[$element["Name"]] = Evaluator::evaluate( + $element["Formula"], + array_merge($values, array("context" => $this->_getContext())) + ); } } @@ -194,25 +303,50 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument return json_encode($this->instrument); } - function display() { + /** + * Returns a HTML representation of the currently instantiated + * instrument. + * + * @return string containing valid HTML + */ + function display() + { $db =& \Database::singleton(); $smarty = new \Smarty_NeuroDB(); $smarty->assign('instrumentJSON', htmlspecialchars($this->toJSON())); - $smarty->assign('initialData', htmlspecialchars(json_encode(\NDB_BVL_Instrument::loadInstanceData($this)))); - $smarty->assign('context', htmlspecialchars(json_encode($this->_getContext()))); + $smarty->assign( + 'initialData', + htmlspecialchars( + json_encode(\NDB_BVL_Instrument::loadInstanceData($this)) + ) + ); + $smarty->assign( + 'context', + htmlspecialchars(json_encode($this->_getContext())) + ); $smarty->assign('lang', htmlspecialchars($this->_getLang())); $html = $smarty->fetch("instrument_react.tpl"); return $html; } - function save() { - $db =& \Database::singleton(); + /** + * Saves the instrument + * + * @return boolean + */ + function save() + { + $db =& \Database::singleton(); $isDataSubmission = isset($_POST['instrumentData']); if ($isDataSubmission) { - $frontEndValues = json_decode($_POST['instrumentData'], true); + $frontEndValues = json_decode($_POST['instrumentData'], true); $calculatedValues = $this->calculateFields($frontEndValues); - $values = array_merge(array(), $frontEndValues, $calculatedValues); + $values = array_merge( + array(), + $frontEndValues, + $calculatedValues + ); if ($this->incompleteExists($values)) { throw new \Exception("A required field was left incomplete", 400); } @@ -242,77 +376,149 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument return true; } - static private function checkForCircularRefs($formulaMap) { - foreach ($formulaMap as $key => $formula) { - $encounteredVars = []; - array_push($encounteredVars, $key); - $res = self::referenceTree($formula, $formulaMap, $encounteredVars); - if($res !== false) - throw new \Exception ("Circular reference at $res"); - } + /** + * Determines whether an circular reference exists in instrument + * + * @param array $formulaMap Formula Map + * + * @return none + */ + static private function _checkForCircularRefs($formulaMap) + { + foreach ($formulaMap as $key => $formula) { + $encounteredVars = []; + array_push($encounteredVars, $key); + $res = self::_referenceTree($formula, $formulaMap, $encounteredVars); + if ($res !== false) { + throw new \Exception("Circular reference at $res"); + } + } } - static private function referenceTree($formula, $formulaMap, $encounteredVars) { - preg_match_all("/\[\w+\]*/", $formula, $matches); - - $variables = array_unique(array_map(function($match) { - return substr($match, 1, -1); - }, $matches[0])); - - foreach ($variables as $variable) { - if (array_key_exists($variable, $formulaMap)) { - if (in_array($variable, $encounteredVars)) - return "$variable"; - - array_push($encounteredVars, $variable); - - return self::referenceTree($formulaMap[$variable], $formulaMap, $encounteredVars); + /** + * TODO + * + * @param string $formula Formula + * @param array $formulaMap Formula Map + * @param array $encounteredVars Encountered Vars + * + * @return TODO + */ + static private function _referenceTree($formula, $formulaMap, $encounteredVars) + { + preg_match_all("/\[\w+\]*/", $formula, $matches); + + $variables = array_unique( + array_map( + function ($match) { + return substr($match, 1, -1); + }, + $matches[0] + ) + ); + + foreach ($variables as $variable) { + if (array_key_exists($variable, $formulaMap)) { + if (in_array($variable, $encounteredVars)) { + return "$variable"; + } + + array_push($encounteredVars, $variable); + + return self::_referenceTree( + $formulaMap[$variable], + $formulaMap, + $encounteredVars + ); + } } - } - - return false; + + return false; } - static function inlineCalcFormulas($elements) { - $calcElements = array_filter($elements, function ($element) { - return $element["Type"] === 'score'; - }); - - $formulaMap = array_reduce($calcElements, function ($result, $element) { - $result[$element["Name"]] = $element["Formula"]; - return $result; - }, array()); - - self::checkForCircularRefs($formulaMap); - - return array_map(function ($element) use ($formulaMap) { - if ($element["Type"] !== 'score') { return $element; } - $recusivelyInlinedFormula = $element["Formula"]; - $resultIsSame = false; - while (!$resultIsSame) { - $result = self::inlineSubFormulas($recusivelyInlinedFormula, $formulaMap); - $resultIsSame = $result === $recusivelyInlinedFormula; - $recusivelyInlinedFormula = $result; - } - $element["Formula"] = $recusivelyInlinedFormula; - return $element; - }, $elements); + /** + * Inlines all references to score fields + * + * @param array $elements Elements + * + * @return array Inlined Elements + */ + static function inlineCalcFormulas($elements) + { + $calcElements = array_filter( + $elements, + function ($element) { + return $element["Type"] === 'score'; + } + ); + + $formulaMap = array_reduce( + $calcElements, + function ($result, $element) { + $result[$element["Name"]] = $element["Formula"]; + return $result; + }, + array() + ); + + self::_checkForCircularRefs($formulaMap); + + return array_map( + function ($element) use ($formulaMap) { + if ($element["Type"] !== 'score') { + return $element; + } + $recusivelyInlinedFormula = $element["Formula"]; + $resultIsSame = false; + while (!$resultIsSame) { + $result = self::_inlineSubFormulas( + $recusivelyInlinedFormula, + $formulaMap + ); + $resultIsSame = $result === $recusivelyInlinedFormula; + $recusivelyInlinedFormula = $result; + } + $element["Formula"] = $recusivelyInlinedFormula; + return $element; + }, + $elements + ); } - static private function inlineSubFormulas($formula, $formulaMap) { + /** + * Inlines all references to score fields + * + * @param array $formula Formula + * @param array $formulaMap Formula Map + * + * @return array Inlined Elements + */ + static private function _inlineSubFormulas($formula, $formulaMap) + { preg_match_all("/\[\w+\]*/", $formula, $matches); - $variables = array_map(function($match) { - return substr($match, 1, -1); - }, $matches[0]); + $variables = array_map( + function ($match) { + return substr($match, 1, -1); + }, + $matches[0] + ); - $inlinedFormula = array_reduce($variables, function($result, $variable) use ($formulaMap) { - if (array_key_exists($variable, $formulaMap)) { - return preg_replace("/\[{$variable}\]/", "({$formulaMap[$variable]})", $result); - } else { - return $result; - } - }, $formula); + $inlinedFormula = array_reduce( + $variables, + function ($result, $variable) use ($formulaMap) { + if (array_key_exists($variable, $formulaMap)) { + return preg_replace( + "/\[{$variable}\]/", + "({$formulaMap[$variable]})", + $result + ); + } else { + return $result; + } + }, + $formula + ); return $inlinedFormula; } From 61f96ab3e179f990d661fd7e9ab8eae604608e09 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Thu, 14 Sep 2017 13:46:28 -0400 Subject: [PATCH 210/214] Fix linting issues --- htdocs/survey-react.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/htdocs/survey-react.php b/htdocs/survey-react.php index efe99ad7fa7..2aea35e5211 100644 --- a/htdocs/survey-react.php +++ b/htdocs/survey-react.php @@ -248,9 +248,9 @@ function display() $base = $config->getSetting('base'); $logo = "/".$config->getSetting('studylogo'); $study = $config->getSetting('title'); - $db =& \Database::singleton(); + $db =& \Database::singleton(); $isDataSubmission = isset($_POST['instrumentData']); - $instrument = new NDB_BVL_Instrument_JSON(); + $instrument = new NDB_BVL_Instrument_JSON(); $instrument->setup($this->CommentID); $instrument->setOptions(array('surveyMode' => true)); @@ -269,21 +269,21 @@ function display() $db->update( $this->TestName, array( - 'Date_taken' => date('Y-m-d'), + 'Date_taken' => date('Y-m-d'), ), array( - 'CommentID' => $this->CommentID, + 'CommentID' => $this->CommentID, ) ); $db->update( 'flag', array( - 'Data_entry' => 'Complete', - 'Administration' => 'All', + 'Data_entry' => 'Complete', + 'Administration' => 'All', ), array( - 'CommentID' => $this->CommentID, + 'CommentID' => $this->CommentID, ) ); } @@ -292,8 +292,14 @@ function display() $smarty->assign($this->tpl_data); $smarty->assign('instrumentJSON', htmlspecialchars($instrument->toJSON())); - $smarty->assign('initialData', htmlspecialchars(json_encode($instrument->_getInstrumentData($db)))); - $smarty->assign('context', htmlspecialchars(json_encode($instrument->_getContext()))); + $smarty->assign( + 'initialData', + htmlspecialchars(json_encode($instrument->_getInstrumentData($db))) + ); + $smarty->assign( + 'context', + htmlspecialchars(json_encode($instrument->_getContext())) + ); $smarty->assign('lang', htmlspecialchars($instrument->_getLang())); $smarty->assign('logo', htmlspecialchars($logo)); $smarty->assign('study', htmlspecialchars($study)); From 99281c6f0ba964640ef6073b5cbd95de96491499 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Fri, 15 Sep 2017 11:55:58 -0400 Subject: [PATCH 211/214] Update inlineCalcFormulas to inlineScoreFormulas --- php/libraries/NDB_BVL_Instrument_JSON.class.inc | 4 ++-- test/unittests/NDB_BVL_Instrument_JSON.php | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/php/libraries/NDB_BVL_Instrument_JSON.class.inc b/php/libraries/NDB_BVL_Instrument_JSON.class.inc index e90aa863f64..b4f0fda0c1f 100644 --- a/php/libraries/NDB_BVL_Instrument_JSON.class.inc +++ b/php/libraries/NDB_BVL_Instrument_JSON.class.inc @@ -120,7 +120,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument $json = file_get_contents($filename); $this->instrumentJSON = $json; $this->instrument = json_decode($json, true); - $this->instrument["Elements"] = self::inlineCalcFormulas( + $this->instrument["Elements"] = self::inlineScoreFormulas( $this->instrument["Elements"] ); $this->table = $this->instrument['Meta']['ShortName']; @@ -443,7 +443,7 @@ class NDB_BVL_Instrument_JSON extends \NDB_BVL_Instrument * * @return array Inlined Elements */ - static function inlineCalcFormulas($elements) + static function inlineScoreFormulas($elements) { $calcElements = array_filter( $elements, diff --git a/test/unittests/NDB_BVL_Instrument_JSON.php b/test/unittests/NDB_BVL_Instrument_JSON.php index 4773af80a9e..68e80816499 100644 --- a/test/unittests/NDB_BVL_Instrument_JSON.php +++ b/test/unittests/NDB_BVL_Instrument_JSON.php @@ -27,7 +27,7 @@ protected function setUp() parent::setUp(); } - public function testInlineCalcFormulas() + public function testInlineScoreFormulas() { $ELEMENTS = [ @@ -37,17 +37,17 @@ public function testInlineCalcFormulas() ], [ "Name" => "b", - "Type" => "calc", + "Type" => "score", "Formula" => "2 + 2", ], [ "Name" => "c", - "Type" => "calc", + "Type" => "score", "Formula" => "[a] + [b]", ], [ "Name" => "d", - "Type" => "calc", + "Type" => "score", "Formula" => "[c] - 3", ] ]; @@ -59,24 +59,24 @@ public function testInlineCalcFormulas() ], [ "Name" => "b", - "Type" => "calc", + "Type" => "score", "Formula" => "2 + 2", ], [ "Name" => "c", - "Type" => "calc", + "Type" => "score", "Formula" => "[a] + (2 + 2)", ], [ "Name" => "d", - "Type" => "calc", + "Type" => "score", "Formula" => "([a] + (2 + 2)) - 3" ] ]; $this->assertEquals( $EXPECTED, - \Loris\Behavioural\NDB_BVL_Instrument_JSON::inlineCalcFormulas($ELEMENTS) + \Loris\Behavioural\NDB_BVL_Instrument_JSON::inlineScoreFormulas($ELEMENTS) ); } From 278e887175ed4549aab1b7a74b3e62d7272f5e13 Mon Sep 17 00:00:00 2001 From: Jacob Penny Date: Fri, 15 Sep 2017 12:24:29 -0400 Subject: [PATCH 212/214] Eslint --- .eslintignore | 3 + jsx/Form.js | 36 +- jsx/InstrumentForm.js | 58 +- jsx/InstrumentFormContainer.js | 82 ++- jsx/lib/Parser/index.js | 4 +- jsx/lib/Parser/js/Evaluator.js | 84 +-- jsx/lib/Parser/js/Functions.js | 86 +-- jsx/lib/Parser/js/logicParser.js | 1062 ++++++++++++++++-------------- jsx/lib/localize-instrument.js | 36 +- 9 files changed, 762 insertions(+), 689 deletions(-) diff --git a/.eslintignore b/.eslintignore index ddad9f8958c..732c9d3b7c6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,6 +5,9 @@ htdocs/js/components/* # Ignore until ESLint is run modules/dataquery/ +# Ignore generated parser +jsx/lib/Parser/js/logicParser.js + # Ignore external libs htdocs/js/flot/* htdocs/js/jquery/* diff --git a/jsx/Form.js b/jsx/Form.js index 31112d45e10..2f40de38397 100644 --- a/jsx/Form.js +++ b/jsx/Form.js @@ -140,20 +140,24 @@ var FormElement = React.createClass({ * RadioGroupLabels * Aligned labels for rows of radio buttons */ -const RadioGroupLabels = ({ labels }) => ( -
- -
- {labels.map((label, index) => ( -
- {label} +const RadioGroupLabels = React.createClass({ + render() { + return ( +
+ +
+ {this.props.labels.map((label, index) => ( +
+ {label} +
+ ))}
- ))} -
-
-); +
+ ); + } +}); /** * RadioGroup Component @@ -163,8 +167,8 @@ const RadioGroupElement = React.createClass({ propTypes: { name: React.PropTypes.string.isRequired, options: React.PropTypes.oneOfType([ - React.PropTypes.object, - React.PropTypes.array + React.PropTypes.object, + React.PropTypes.array ]), label: React.PropTypes.string, value: React.PropTypes.string, @@ -299,7 +303,6 @@ const CheckboxGroupElement = React.createClass({ render: function() { var required = this.props.required ? 'required' : null; - var disabled = this.props.disabled ? 'disabled' : null; var options = this.props.options; var errorMessage = null; var requiredHTML = null; @@ -345,7 +348,6 @@ const CheckboxGroupElement = React.createClass({ } }); - /** * Select Component * React wrapper for a simple or 'multiple'