From 42580f8973b29e9148068623c3e0213140178398 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Thu, 23 Apr 2020 11:26:23 -0400 Subject: [PATCH 01/60] WIP --- modules/datadict/php/datadict.class.inc | 2 +- .../php/datadictrowprovisioner.class.inc | 63 +++++++++++++------ php/libraries/Database.class.inc | 2 +- php/libraries/LorisForm.class.inc | 6 +- php/libraries/Module.class.inc | 2 + php/libraries/NDB_Page.class.inc | 4 ++ 6 files changed, 56 insertions(+), 23 deletions(-) diff --git a/modules/datadict/php/datadict.class.inc b/modules/datadict/php/datadict.class.inc index b680b8f7c64..14e783442d4 100644 --- a/modules/datadict/php/datadict.class.inc +++ b/modules/datadict/php/datadict.class.inc @@ -100,7 +100,7 @@ class Datadict extends \DataFrameworkMenu */ public function getBaseDataProvisioner() : \LORIS\Data\Provisioner { - return new DataDictRowProvisioner(); + return new DataDictRowProvisioner($this->lorisinstance); } /** diff --git a/modules/datadict/php/datadictrowprovisioner.class.inc b/modules/datadict/php/datadictrowprovisioner.class.inc index ac344df3711..8e4eceaa40a 100644 --- a/modules/datadict/php/datadictrowprovisioner.class.inc +++ b/modules/datadict/php/datadictrowprovisioner.class.inc @@ -28,33 +28,58 @@ namespace LORIS\datadict; * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 * @link https://www.github.com/aces/Loris/ */ -class DataDictRowProvisioner extends \LORIS\Data\Provisioners\DBRowProvisioner +class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance { /** * Create a DataDictRowProvisioner, which gets rows for * the datadict menu table. */ - function __construct() + function __construct(\LORIS\LorisInstance $loris) { - parent::__construct( - "SELECT DISTINCT - pt.sourceFrom as source_from, - pt.name as name, - pt.sourceField as source_field, - coalesce(pto.description,pt.description) as description, - CASE - WHEN COALESCE(pto.description,pt.description) = '' THEN 'Empty' - WHEN pto.name IS NOT NULL THEN 'Modified' - WHEN pto.name IS NULL THEN 'Unchanged' - END as description_status - FROM parameter_type pt - LEFT JOIN parameter_type_override pto USING (Name) - WHERE pt.Queryable=1 - ", - [] - ); + $this->loris = $loris; } + public function getAllInstances() : \Traversable { + $modules = $this->loris->getActiveModules(); + $DB = $this->loris->getDatabaseConnection(); + + $dict = []; + + $overrides = $DB->pselectWithIndexKey( + "SELECT Name, Description FROM parameter_type_override", + [], + "Name" + ); + + foreach ($modules as $module) { + $mdict = $module->getDataDictionary($this->loris); + $dict = array_merge($dict, array_map(function($item) use ($overrides) { + $name = $item->getCategory()->getName() . '_' . $item->getName(); + $desc = ''; + $status = 'Unchanged'; + if(isset($overrides[$name])) { + $desc = $overrides[$name]['Description']; + $status = 'Modified'; + } else { + $desc = $item->getDescription(); + } + + if ($desc == '') { + $status = 'Empty'; + } + return $this->getInstance( + [ + 'source_from' => $item->getCategory()->getName(), + 'name' => $name, + 'source_field' => $item->getName(), + 'description' => $desc, + 'description_status' => $status, + ] + ); + }, $mdict)); + } + return new \ArrayIterator($dict); + } /** * Returns an instance of a DataDict object for a given * table row. diff --git a/php/libraries/Database.class.inc b/php/libraries/Database.class.inc index 859b0d8d8dc..10639b70447 100644 --- a/php/libraries/Database.class.inc +++ b/php/libraries/Database.class.inc @@ -177,7 +177,7 @@ class Database . "and the supplied password." ); } - $this->_PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + $this->_PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->_PDO->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $this->_preparedStoreHistory = $this->_PDO->prepare( diff --git a/php/libraries/LorisForm.class.inc b/php/libraries/LorisForm.class.inc index c7be45c73cf..594a2cc47e3 100644 --- a/php/libraries/LorisForm.class.inc +++ b/php/libraries/LorisForm.class.inc @@ -148,7 +148,7 @@ class LorisForm $el['type'] = 'select'; $el['options'] = $options; - if (isset($attribs['multiple']) || in_array('multiple', $attribs, true)) { + if (isset($attribs['multiple'])) { $el['multiple'] = 'multiple'; } return $el; @@ -912,7 +912,9 @@ class LorisForm } $element['html'] = $this->renderElement($element); - $retVal .= $element['html']; + if (!is_array($element['html'])) { + $retVal .= $element['html']; + } if (isset($el['options']['postfix_wrapper'])) { $retVal .= $el['options']['postfix_wrapper']; diff --git a/php/libraries/Module.class.inc b/php/libraries/Module.class.inc index b1b38043266..332877dcca1 100644 --- a/php/libraries/Module.class.inc +++ b/php/libraries/Module.class.inc @@ -35,6 +35,7 @@ abstract class Module extends \LORIS\Router\PrefixRouter . "your project administrator and/or verify your LORIS configuration."; protected $name; protected $dir; + protected $lorisinstance; /** * Retrieve all active module descriptors from the given database @@ -329,6 +330,7 @@ abstract class Module extends \LORIS\Router\PrefixRouter return $resp; } + $this->lorisinstance = $request->getAttribute("loris"); $pagename = $this->getName(); $path = trim($request->getURI()->getPath(), "/"); if (!empty($path)) { diff --git a/php/libraries/NDB_Page.class.inc b/php/libraries/NDB_Page.class.inc index 8e729e87017..15317724039 100644 --- a/php/libraries/NDB_Page.class.inc +++ b/php/libraries/NDB_Page.class.inc @@ -28,6 +28,7 @@ use \Psr\Http\Message\ResponseInterface; */ class NDB_Page implements RequestHandlerInterface { + protected $lorisinstance; /** * The name of the test_name being accessed */ @@ -684,6 +685,9 @@ class NDB_Page implements RequestHandlerInterface )->withStatus(403); } + $loris = $request->getAttribute("loris"); + $this->lorisinstance = $loris; + // The NDB_Page object itself is sometimes required by middleware, // but since the PSR15 Middleware interface is fixed, it needs to be // carried along with the request object. We use a pageclass attribute From 20847d02beb3eb2a91ab26c94dd91cc17a211468 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Thu, 23 Apr 2020 12:30:25 -0400 Subject: [PATCH 02/60] WIP --- php/libraries/NDB_BVL_Instrument_LINST.class.inc | 7 +++++++ src/Data/Dictionary/DictionaryType.php | 10 ++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/Data/Dictionary/DictionaryType.php diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 0cb05321408..fb5f133d57d 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -609,6 +609,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument } else { $this->addTextElement($pieces[1], $pieces[2]); } + $this->dictionary[] = new DictionaryItem($pieces[1], $pieces[2], $dictcat); } $this->dictionary[] = new DictionaryItem( $pieces[1], @@ -634,6 +635,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument } else { $this->addTextAreaElement($pieces[1], $pieces[2]); } + $this->dictionary[] = new DictionaryItem($pieces[1], $pieces[2], $dictcat); } $this->dictionary[] = new DictionaryItem( $pieces[1], @@ -662,6 +664,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument 'emptyOptionValue' => null, ]; } + $this->dictionary[] = new DictionaryItem($pieces[1], $pieces[2], $dictcat); $this->dictionary[] = new DictionaryItem( $pieces[1], @@ -1146,6 +1149,10 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument return $this->fullName; } + public function getDataDictionary() : iterable { + return $this->dictionary; + } + /** * Returns the dictionary for this instrument. * diff --git a/src/Data/Dictionary/DictionaryType.php b/src/Data/Dictionary/DictionaryType.php new file mode 100644 index 00000000000..d6ab62322a6 --- /dev/null +++ b/src/Data/Dictionary/DictionaryType.php @@ -0,0 +1,10 @@ + Date: Thu, 23 Apr 2020 12:48:03 -0400 Subject: [PATCH 03/60] Add metadata fields to linst files --- php/libraries/NDB_BVL_Instrument_LINST.class.inc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index fb5f133d57d..5c6e4d54710 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -541,6 +541,16 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument // the feature is completely deprecated. $this->testName = $this->testName ?? trim($pieces[1]); $this->table = $this->table ?? trim($pieces[1]); + $dictcat = new \LORIS\Data\Dictionary\Category($this->testName, $this->getFullName()); + // Now that we have the category, add things that are added by addMetaDatafields() but + // aren't in the .linst file + $this->dictionary = array_merge($this->dictionary, + [ + new DictionaryItem('Date_taken', 'Date of Administration', $dictcat), + new DictionaryItem('Candidate_age', 'Candidate Age (Months)', $dictcat), + new DictionaryItem('Window_Difference', 'Window difference from test battery (days)', $dictcat), + new DictionaryItem('Examiner', 'Examiner', $dictcat), + ]); break; case 'title': $this->fullName = $pieces[1]; From 88479b42c11aa357d1e532ab87041e6da93d61e4 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 15 Jun 2020 14:02:44 -0400 Subject: [PATCH 04/60] [Core] Require correct types for lorisform->addSelect Since the type is currently only enforced by PHPDocs and not PHP, incorrect types can be passed by addSelect. This enforces the types from the PHP side of things. (Currently when a string is passed instread of an array in some instruments, the array_merge can generated a PHP warning which breaks endpoints which use an instrument in a JSON context.) --- php/libraries/NDB_Page.class.inc | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/php/libraries/NDB_Page.class.inc b/php/libraries/NDB_Page.class.inc index 15317724039..0813d2d34b3 100644 --- a/php/libraries/NDB_Page.class.inc +++ b/php/libraries/NDB_Page.class.inc @@ -198,12 +198,8 @@ class NDB_Page implements RequestHandlerInterface * * @return void */ - function addSelect( - string $name, - string $label, - array $options, - array $attribs=[] - ) { + function addSelect(string $name, string $label, array $options, array $attribs=array()) + { $attribs = array_merge( ['class' => 'form-control input-sm'], $attribs From 6139b531562b2e62a577232d9eff9476f0ee416e Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 15 Jun 2020 14:14:58 -0400 Subject: [PATCH 05/60] PHPCS --- .../php/datadictrowprovisioner.class.inc | 68 +++++++++++------- php/libraries/Module.class.inc | 4 +- .../NDB_BVL_Instrument_LINST.class.inc | 70 +++++++++++++++---- php/libraries/NDB_Page.class.inc | 8 ++- 4 files changed, 108 insertions(+), 42 deletions(-) diff --git a/modules/datadict/php/datadictrowprovisioner.class.inc b/modules/datadict/php/datadictrowprovisioner.class.inc index 8e4eceaa40a..5de5a25d5ed 100644 --- a/modules/datadict/php/datadictrowprovisioner.class.inc +++ b/modules/datadict/php/datadictrowprovisioner.class.inc @@ -33,15 +33,25 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance /** * Create a DataDictRowProvisioner, which gets rows for * the datadict menu table. + * + * @param \LORIS\LorisInstance $loris The Loris instance whose + * dictionary should be retrieved */ function __construct(\LORIS\LorisInstance $loris) { $this->loris = $loris; } - public function getAllInstances() : \Traversable { + /** + * Get all dictionary instances, honouring parameter_type_override + * if applicable + * + * @return \Traversable + */ + public function getAllInstances() : \Traversable + { $modules = $this->loris->getActiveModules(); - $DB = $this->loris->getDatabaseConnection(); + $DB = $this->loris->getDatabaseConnection(); $dict = []; @@ -53,30 +63,38 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance foreach ($modules as $module) { $mdict = $module->getDataDictionary($this->loris); - $dict = array_merge($dict, array_map(function($item) use ($overrides) { - $name = $item->getCategory()->getName() . '_' . $item->getName(); - $desc = ''; - $status = 'Unchanged'; - if(isset($overrides[$name])) { - $desc = $overrides[$name]['Description']; - $status = 'Modified'; - } else { - $desc = $item->getDescription(); - } + $dict = array_merge( + $dict, + array_map( + function ($item) use ($overrides) { + $name = $item->getCategory()->getName() + . '_' . $item->getName(); + $desc = ''; + $status = 'Unchanged'; + if (isset($overrides[$name])) { + $desc = $overrides[$name]['Description']; + $status = 'Modified'; + } else { + $desc = $item->getDescription(); + } - if ($desc == '') { - $status = 'Empty'; - } - return $this->getInstance( - [ - 'source_from' => $item->getCategory()->getName(), - 'name' => $name, - 'source_field' => $item->getName(), - 'description' => $desc, - 'description_status' => $status, - ] - ); - }, $mdict)); + if ($desc == '') { + $status = 'Empty'; + } + return $this->getInstance( + [ + 'source_from' => + $item->getCategory()->getName(), + 'name' => $name, + 'source_field' => $item->getName(), + 'description' => $desc, + 'description_status' => $status, + ] + ); + }, + $mdict + ) + ); } return new \ArrayIterator($dict); } diff --git a/php/libraries/Module.class.inc b/php/libraries/Module.class.inc index 332877dcca1..3e6cc2fae9d 100644 --- a/php/libraries/Module.class.inc +++ b/php/libraries/Module.class.inc @@ -331,8 +331,8 @@ abstract class Module extends \LORIS\Router\PrefixRouter } $this->lorisinstance = $request->getAttribute("loris"); - $pagename = $this->getName(); - $path = trim($request->getURI()->getPath(), "/"); + $pagename = $this->getName(); + $path = trim($request->getURI()->getPath(), "/"); if (!empty($path)) { // There is a subpage $pagename = explode("/", $path)[0]; diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 5c6e4d54710..7b3d0be5f76 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -541,16 +541,42 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument // the feature is completely deprecated. $this->testName = $this->testName ?? trim($pieces[1]); $this->table = $this->table ?? trim($pieces[1]); - $dictcat = new \LORIS\Data\Dictionary\Category($this->testName, $this->getFullName()); + // Now that we have the category, add things that are added by addMetaDatafields() but // aren't in the .linst file $this->dictionary = array_merge($this->dictionary, + $dictcat = new \LORIS\Data\Dictionary\Category( + $this->testName, + $this->getFullName() + ); + + // Now that we have the category, add things that are added by + // addMetaDatafields() but aren't in the .linst file + $this->dictionary = array_merge( + $this->dictionary, [ - new DictionaryItem('Date_taken', 'Date of Administration', $dictcat), - new DictionaryItem('Candidate_age', 'Candidate Age (Months)', $dictcat), - new DictionaryItem('Window_Difference', 'Window difference from test battery (days)', $dictcat), - new DictionaryItem('Examiner', 'Examiner', $dictcat), - ]); + new DictionaryItem( + 'Date_taken', + 'Date of Administration', + $dictcat + ), + new DictionaryItem( + 'Candidate_age', + 'Candidate Age (Months)', + $dictcat + ), + new DictionaryItem( + 'Window_Difference', + 'Window difference from test battery (days)', + $dictcat + ), + new DictionaryItem( + 'Examiner', + 'Examiner', + $dictcat + ), + ] + ); break; case 'title': $this->fullName = $pieces[1]; @@ -619,7 +645,11 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument } else { $this->addTextElement($pieces[1], $pieces[2]); } - $this->dictionary[] = new DictionaryItem($pieces[1], $pieces[2], $dictcat); + $this->dictionary[] = new DictionaryItem( + $pieces[1], + $pieces[2], + $dictcat + ); } $this->dictionary[] = new DictionaryItem( $pieces[1], @@ -645,7 +675,11 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument } else { $this->addTextAreaElement($pieces[1], $pieces[2]); } - $this->dictionary[] = new DictionaryItem($pieces[1], $pieces[2], $dictcat); + $this->dictionary[] = new DictionaryItem( + $pieces[1], + $pieces[2], + $dictcat, + ); } $this->dictionary[] = new DictionaryItem( $pieces[1], @@ -674,7 +708,11 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument 'emptyOptionValue' => null, ]; } - $this->dictionary[] = new DictionaryItem($pieces[1], $pieces[2], $dictcat); + $this->dictionary[] = new DictionaryItem( + $pieces[1], + $pieces[2], + $dictcat + ); $this->dictionary[] = new DictionaryItem( $pieces[1], @@ -1159,10 +1197,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument return $this->fullName; } - public function getDataDictionary() : iterable { - return $this->dictionary; - } - /** * Returns the dictionary for this instrument. * @@ -1185,4 +1219,14 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument { return $this->subtests; } + + /* + * Returns the dictionary for this instrument. + * + * @return \LORIS\Data\Dictionary\DictionaryItem[] + */ + public function getDataDictionary() : iterable + { + return $this->dictionary; + } } diff --git a/php/libraries/NDB_Page.class.inc b/php/libraries/NDB_Page.class.inc index 0813d2d34b3..9cf0cc61682 100644 --- a/php/libraries/NDB_Page.class.inc +++ b/php/libraries/NDB_Page.class.inc @@ -198,8 +198,12 @@ class NDB_Page implements RequestHandlerInterface * * @return void */ - function addSelect(string $name, string $label, array $options, array $attribs=array()) - { + function addSelect( + string $name, + string $label, + array $options, + array $attribs=array() + ) { $attribs = array_merge( ['class' => 'form-control input-sm'], $attribs From 084a013501323fad3ca65df4ea0263be223b3dce Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 15 Jun 2020 15:48:02 -0400 Subject: [PATCH 06/60] phan --- php/libraries/NDB_BVL_Instrument_LINST.class.inc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 7b3d0be5f76..b38c934d4c0 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -497,6 +497,14 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument 'Delimiter' => $this->_GUIDelimiter, ]; + // This needs to be initialized to a valid category, phan doesn't + // know enough about the LINST format to know that the "table" case + // is always encountered before the other dictionary items. + $dictcat = new \LORIS\Data\Dictionary\Category( + "Unknown", + "Unknown Category" + ); + while (($line = fgets($fp, 4096)) !== false) { $pieces = preg_split("/{@}/", $line); $this->LinstLines[] = $pieces; From 31fe6b7110826452e8d497eec4d97740cd5d5c14 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Tue, 16 Jun 2020 11:43:45 -0400 Subject: [PATCH 07/60] Remove unused class --- src/Data/Dictionary/DictionaryType.php | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/Data/Dictionary/DictionaryType.php diff --git a/src/Data/Dictionary/DictionaryType.php b/src/Data/Dictionary/DictionaryType.php deleted file mode 100644 index d6ab62322a6..00000000000 --- a/src/Data/Dictionary/DictionaryType.php +++ /dev/null @@ -1,10 +0,0 @@ - Date: Tue, 16 Jun 2020 11:58:14 -0400 Subject: [PATCH 08/60] Add scope to dictionary --- .../NDB_BVL_Instrument_LINST.class.inc | 19 +++++++++++++------ src/Data/Dictionary/DictionaryItem.php | 5 +++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index b38c934d4c0..3a4b0c30a91 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -566,22 +566,26 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument new DictionaryItem( 'Date_taken', 'Date of Administration', - $dictcat + $dictcat, + $scope, ), new DictionaryItem( 'Candidate_age', 'Candidate Age (Months)', - $dictcat + $dictcat, + $scope, ), new DictionaryItem( 'Window_Difference', 'Window difference from test battery (days)', - $dictcat + $dictcat, + $scope, ), new DictionaryItem( 'Examiner', 'Examiner', - $dictcat + $dictcat, + $scope, ), ] ); @@ -656,7 +660,8 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $this->dictionary[] = new DictionaryItem( $pieces[1], $pieces[2], - $dictcat + $dictcat, + $scope, ); } $this->dictionary[] = new DictionaryItem( @@ -687,6 +692,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $pieces[1], $pieces[2], $dictcat, + $scope, ); } $this->dictionary[] = new DictionaryItem( @@ -719,7 +725,8 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $this->dictionary[] = new DictionaryItem( $pieces[1], $pieces[2], - $dictcat + $dictcat, + $scope, ); $this->dictionary[] = new DictionaryItem( diff --git a/src/Data/Dictionary/DictionaryItem.php b/src/Data/Dictionary/DictionaryItem.php index 44015061007..69520dc92c5 100644 --- a/src/Data/Dictionary/DictionaryItem.php +++ b/src/Data/Dictionary/DictionaryItem.php @@ -1,6 +1,7 @@ scope; + } } From a5f39c64a0cb1a3394888ebdbded032955b50668 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Tue, 16 Jun 2020 13:15:41 -0400 Subject: [PATCH 09/60] Add data scope to datadict frontend --- modules/datadict/jsx/dataDictIndex.js | 13 +++++++++++++ .../datadict/php/datadictrowprovisioner.class.inc | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/datadict/jsx/dataDictIndex.js b/modules/datadict/jsx/dataDictIndex.js index 808063b4e8d..ee0033e79ca 100644 --- a/modules/datadict/jsx/dataDictIndex.js +++ b/modules/datadict/jsx/dataDictIndex.js @@ -178,6 +178,19 @@ class DataDictIndex extends Component { }, }, }, + { + label: 'Data Scope', + show: true, + filter: { + name: 'datascope', + type: 'select', + options: { + 'candidate': 'Candidate', + 'session': 'Session', + 'project': 'Project', + }, + }, + }, ]; return ( getInstance( [ - 'source_from' => - $item->getCategory()->getName(), + 'source_from' => $item->getCategory()->getName(), 'name' => $name, 'source_field' => $item->getName(), 'description' => $desc, 'description_status' => $status, + 'datascope' => $item->getScope(), ] ); }, From c0c801cffa979963a3d3b21e06043977550e0584 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Tue, 16 Jun 2020 15:19:55 -0400 Subject: [PATCH 10/60] Add data type to dictionary --- src/Data/Dictionary/DictionaryItem.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Data/Dictionary/DictionaryItem.php b/src/Data/Dictionary/DictionaryItem.php index 69520dc92c5..29459465587 100644 --- a/src/Data/Dictionary/DictionaryItem.php +++ b/src/Data/Dictionary/DictionaryItem.php @@ -120,4 +120,8 @@ public function isAccessibleBy(\User $user): bool public function getScope() : Scope { return $this->scope; } + + public function getDataType() : \LORIS\Data\Type { + return $this->typ; + } } From 7aa4a10bea1545b757393f1c341bbd5739c87246 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 8 Jul 2020 10:31:13 -0400 Subject: [PATCH 11/60] Add Data types --- php/libraries/NDB_BVL_Instrument_LINST.class.inc | 13 +++++++++++++ src/Data/Dictionary/DictionaryItem.php | 1 + 2 files changed, 14 insertions(+) diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 3a4b0c30a91..71d07f66d21 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -20,6 +20,11 @@ use \LORIS\Data\Types\IntegerType; use \LORIS\Data\Types\Enumeration; use \LORIS\Data\Types\DateType; +use \LORIS\Data\Types\StringType; +use \LORIS\Data\Types\IntegerType; +use \LORIS\Data\Types\Enumeration; +use \LORIS\Data\Types\DateType; + /** * Base class for all NeuroDB behavioural instruments * @@ -568,24 +573,29 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument 'Date of Administration', $dictcat, $scope, + new \LORIS\Data\Types\DateType(), ), new DictionaryItem( 'Candidate_age', 'Candidate Age (Months)', $dictcat, $scope, + new \LORIS\Data\Types\Duration(), ), new DictionaryItem( 'Window_Difference', 'Window difference from test battery (days)', $dictcat, $scope, + new \LORIS\Data\Types\Duration(), ), new DictionaryItem( 'Examiner', 'Examiner', $dictcat, $scope, + // Should this be an enum of examiners? + new StringType(255), ), ] ); @@ -662,6 +672,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $pieces[2], $dictcat, $scope, + new StringType(255), ); } $this->dictionary[] = new DictionaryItem( @@ -693,6 +704,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $pieces[2], $dictcat, $scope, + new StringType(), ); } $this->dictionary[] = new DictionaryItem( @@ -727,6 +739,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $pieces[2], $dictcat, $scope, + new DateType(), ); $this->dictionary[] = new DictionaryItem( diff --git a/src/Data/Dictionary/DictionaryItem.php b/src/Data/Dictionary/DictionaryItem.php index 29459465587..d7305025bc4 100644 --- a/src/Data/Dictionary/DictionaryItem.php +++ b/src/Data/Dictionary/DictionaryItem.php @@ -2,6 +2,7 @@ declare(strict_types=1); namespace LORIS\Data\Dictionary; use \LORIS\Data\Scope; +use \LORIS\Data\Type; use \LORIS\Data\Scope; use \LORIS\Data\Type; From e1cacfbab65993d040cc86ab205d4d9d10825775 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Thu, 9 Jul 2020 09:54:10 -0400 Subject: [PATCH 12/60] WIP --- modules/datadict/jsx/dataDictIndex.js | 97 ++++++++++++++++++- .../php/datadictrowprovisioner.class.inc | 42 ++++---- .../NDB_BVL_Instrument_LINST.class.inc | 18 +--- webpack.config.js | 2 +- 4 files changed, 115 insertions(+), 44 deletions(-) diff --git a/modules/datadict/jsx/dataDictIndex.js b/modules/datadict/jsx/dataDictIndex.js index ee0033e79ca..9ad94986b53 100644 --- a/modules/datadict/jsx/dataDictIndex.js +++ b/modules/datadict/jsx/dataDictIndex.js @@ -2,6 +2,7 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import Loader from 'Loader'; import FilterableDataTable from 'FilterableDataTable'; +import swal from 'sweetalert2'; import fetchDataStream from 'jslib/fetchDataStream'; @@ -36,6 +37,7 @@ class DataDictIndex extends Component { this.fetchData = this.fetchData.bind(this); this.formatColumn = this.formatColumn.bind(this); + this.editSwal = this.editSwal.bind(this); } /** @@ -56,6 +58,87 @@ class DataDictIndex extends Component { this.fetchData(); } + editSwal(row) { + return () => { + swal.fire({ + title: 'Edit Description', + input: 'text', + inputValue: row.Description, + confirmButtonText: 'Modify', + showCancelButton: true, + inputValidator: (value) => { + if (!value) { + return 'Missing description'; + } + }, + }).then((result) => { + if (!result.value) { + return; + } + + const url = loris.BaseURL + '/datadict/fields/' + encodeURI(row.Name); + + // The fetch happens asyncronously, which means that the + // swal closes before it returns. We find the index that + // was being updated and aggressively update it, then + // re-update or reset it when the PUT request returns. + let i; + let odesc; + let ostat; + for (i = 0; i < this.state.data.Data.length; i++) { + if (this.state.data.Data[i][1] == row.Name) { + // Store the original values in case the fetch + // fails and we need to restore them. + odesc = this.state.data.Data[i][3]; + ostat = this.state.data.Data[i][4]; + + // Aggressively update the state and assume + // it's been modified. + this.state.data.Data[i][3] = result.value; + this.state.data.Data[i][4] = 'Modified'; + + // Force a re-render + this.setState({state: this.state}); + break; + } + } + fetch(url, { + method: 'PUT', + credentials: 'same-origin', + cache: 'no-cache', + body: result.value, + }).then((response) => { + if (!response.ok) { + // The response wasn't in the 200-299 range, + // so revert the update we did above and + // force a re-render. + this.state.data.Data[i][3] = odesc; + this.state.data.Data[i][4] = ostat; + + // Force a re-render + this.setState({state: this.state}); + return; + } + + // The response to the PUT request said we're + // good, but it's possible the status was changed + // back to the original. So update the status + // based on what the response said the value was. + this.state.data.Data[i][4] = response.headers.get('X-StatusDesc'); + this.setState({state: this.state}); + }).catch(() => { + // Something went wrong, restore the original + // status and description + this.state.data.Data[i][3] = odesc; + this.state.data.Data[i][4] = ostat; + + // Force a re-render + this.setState({state: this.state}); + }); + }); + }; + } + /** * Retrive data from the provided URL and save it in state */ @@ -112,7 +195,7 @@ class DataDictIndex extends Component { ); } - return {cell}; + return {cell} {edited} {editIcon} ; } /** @@ -143,7 +226,7 @@ class DataDictIndex extends Component { }, { label: 'Name', - show: true, + show: false, filter: { name: 'Name', type: 'text', @@ -167,7 +250,7 @@ class DataDictIndex extends Component { }, { label: 'Description Status', - show: true, + show: false, filter: { name: 'DescriptionStatus', type: 'select', @@ -191,6 +274,14 @@ class DataDictIndex extends Component { }, }, }, + { + label: 'Data Type', + show: true, + filter: { + name: 'datatype', + type: 'text', + }, + }, ]; return ( getDataDictionary($this->loris); - $dict = array_merge( - $dict, - array_map( - function ($item) use ($overrides) { - $name = $item->getCategory()->getName() - . '_' . $item->getName(); - $desc = ''; - $status = 'Unchanged'; - if (isset($overrides[$name])) { - $desc = $overrides[$name]['Description']; - $status = 'Modified'; - } else { - $desc = $item->getDescription(); - } + foreach($mdict as $cat) { + foreach($cat->getItems() as $item) { + $name = $cat->getName() . '_' . $item->getName(); + $desc = ''; + $status = 'Unchanged'; + if (isset($overrides[$name])) { + $desc = $overrides[$name]['Description']; + $status = 'Modified'; + } else { + $desc = $item->getDescription(); + } - if ($desc == '') { - $status = 'Empty'; - } - return $this->getInstance( + if ($desc == '') { + $status = 'Empty'; + } + $dict[] = $this->getInstance( [ - 'source_from' => $item->getCategory()->getName(), + 'source_from' => $cat->getName(), 'name' => $name, 'source_field' => $item->getName(), 'description' => $desc, 'description_status' => $status, 'datascope' => $item->getScope(), + 'type' => $item->getDataType(), ] ); - }, - $mdict - ) - ); + } + } } return new \ArrayIterator($dict); } diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 71d07f66d21..4b046e0cb79 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -502,14 +502,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument 'Delimiter' => $this->_GUIDelimiter, ]; - // This needs to be initialized to a valid category, phan doesn't - // know enough about the LINST format to know that the "table" case - // is always encountered before the other dictionary items. - $dictcat = new \LORIS\Data\Dictionary\Category( - "Unknown", - "Unknown Category" - ); - while (($line = fgets($fp, 4096)) !== false) { $pieces = preg_split("/{@}/", $line); $this->LinstLines[] = $pieces; @@ -557,8 +549,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument // Now that we have the category, add things that are added by addMetaDatafields() but // aren't in the .linst file - $this->dictionary = array_merge($this->dictionary, - $dictcat = new \LORIS\Data\Dictionary\Category( + $this->dictcategory = new \LORIS\Data\Dictionary\Category( $this->testName, $this->getFullName() ); @@ -571,28 +562,24 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument new DictionaryItem( 'Date_taken', 'Date of Administration', - $dictcat, $scope, new \LORIS\Data\Types\DateType(), ), new DictionaryItem( 'Candidate_age', 'Candidate Age (Months)', - $dictcat, $scope, new \LORIS\Data\Types\Duration(), ), new DictionaryItem( 'Window_Difference', 'Window difference from test battery (days)', - $dictcat, $scope, new \LORIS\Data\Types\Duration(), ), new DictionaryItem( 'Examiner', 'Examiner', - $dictcat, $scope, // Should this be an enum of examiners? new StringType(255), @@ -670,7 +657,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $this->dictionary[] = new DictionaryItem( $pieces[1], $pieces[2], - $dictcat, $scope, new StringType(255), ); @@ -702,7 +688,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $this->dictionary[] = new DictionaryItem( $pieces[1], $pieces[2], - $dictcat, $scope, new StringType(), ); @@ -737,7 +722,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $this->dictionary[] = new DictionaryItem( $pieces[1], $pieces[2], - $dictcat, $scope, new DateType(), ); diff --git a/webpack.config.js b/webpack.config.js index 2f0d8958cb7..03466b0b14e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -229,7 +229,6 @@ const config = [ 'onLoad', 'candidateListIndex', ]), - lorisModule('datadict', ['dataDictIndex']), lorisModule('data_release', [ 'dataReleaseIndex', ]), @@ -289,6 +288,7 @@ const config = [ lorisModule('server_processes_manager', ['server_processes_managerIndex']), lorisModule('instruments', ['CandidateInstrumentList']), lorisModule('candidate_profile', ['CandidateInfo']), + lorisModule('datadict', ['dataDictIndex']), ]; // Support project overrides From 046bb3b62600a776cc875aba6e57bea1e54c4f10 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 13 Jul 2020 14:46:52 -0400 Subject: [PATCH 13/60] WIP --- jsx/FilterableDataTable.js | 30 +++++++++++++++ modules/datadict/jsx/dataDictIndex.js | 23 ++++++++++- modules/datadict/php/datadict.class.inc | 38 +++++++++++++++++++ modules/datadict/php/datadictrow.class.inc | 9 ++++- .../php/datadictrowprovisioner.class.inc | 26 +++++++------ php/libraries/Module.class.inc | 3 +- .../NDB_BVL_Instrument_LINST.class.inc | 4 +- php/libraries/NDB_Page.class.inc | 14 +++++++ src/Data/Dictionary/DictionaryItem.php | 17 +++++++++ src/Data/Types/{Duration.php => Interval.php} | 0 10 files changed, 146 insertions(+), 18 deletions(-) rename src/Data/Types/{Duration.php => Interval.php} (100%) diff --git a/jsx/FilterableDataTable.js b/jsx/FilterableDataTable.js index c4542c9c929..f283c3c3253 100644 --- a/jsx/FilterableDataTable.js +++ b/jsx/FilterableDataTable.js @@ -17,10 +17,18 @@ import Filter from 'jsx/Filter'; * Deprecates Filter Form. */ class FilterableDataTable extends Component { +<<<<<<< HEAD /** * @constructor * @param {object} props - React Component properties */ +======= + /** + * x + * + * @param {object} props - x + */ +>>>>>>> WIP constructor(props) { super(props); this.state = { @@ -95,6 +103,28 @@ class FilterableDataTable extends Component { this.updateFilters({}); } + validFilters() { + let filters = {}; + this.props.fields.forEach((field) => { + const filtername = field.filter.name; + const filterval = this.state.filter[filtername]; + if (!this.state.filter[filtername]) { + return; + } + + if (field.filter.type !== 'select') { + filters[filtername] = filterval; + return; + } + + if (!(filterval.value in field.filter.options)) { + return; + } + filters[filtername] = filterval; + }); + return filters; + } + /** * Returns the filter state, with filters that are * set to an invalid option removed from the returned diff --git a/modules/datadict/jsx/dataDictIndex.js b/modules/datadict/jsx/dataDictIndex.js index 9ad94986b53..608f316d3a0 100644 --- a/modules/datadict/jsx/dataDictIndex.js +++ b/modules/datadict/jsx/dataDictIndex.js @@ -38,6 +38,7 @@ class DataDictIndex extends Component { this.fetchData = this.fetchData.bind(this); this.formatColumn = this.formatColumn.bind(this); this.editSwal = this.editSwal.bind(this); + this.updateFilter = this.updateFilter.bind(this); } /** @@ -58,6 +59,16 @@ class DataDictIndex extends Component { this.fetchData(); } + updateFilter(filter) { + if (filter.Module) { + this.setState({moduleFilter: filter.Module.value}); + } else { + this.setState({moduleFilter: ''}); + } + + console.log(filter); + } + editSwal(row) { return () => { swal.fire({ @@ -216,7 +227,16 @@ class DataDictIndex extends Component { const options = this.state.fieldOptions; let fields = [ { - label: 'Source From', + label: 'Module', + show: true, + filter: { + name: 'Module', + type: 'select', + options: options.modules, + }, + }, + { + label: 'Category', show: true, filter: { name: 'Source From', @@ -290,6 +310,7 @@ class DataDictIndex extends Component { fields={fields} loading={this.state.isLoading} getFormattedCell={this.formatColumn} + updateFilterCallback={this.updateFilter} /> ); } diff --git a/modules/datadict/php/datadict.class.inc b/modules/datadict/php/datadict.class.inc index 14e783442d4..67212efd46d 100644 --- a/modules/datadict/php/datadict.class.inc +++ b/modules/datadict/php/datadict.class.inc @@ -11,6 +11,7 @@ * @link https://github.com/aces/Loris-Trunk */ namespace LORIS\datadict; +use \Psr\Http\Message\ServerRequestInterface; /** * Datadict module * @@ -143,4 +144,41 @@ class Datadict extends \DataFrameworkMenu { return false; } + + // FIXME: This should be moved to the parent class + public function getDataProvisionerWithFilters() : \LORIS\Data\Provisioner { + $prov = parent::getDataProvisionerWithFilters(); + return $prov->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); + } + + public function loadResources( + \User $user, ServerRequestInterface $request + ) : void { + $this->lorisinstance = $request->getAttribute("loris"); + $modules = $this->lorisinstance->getActiveModules(); + $usermodules = []; + $dict = []; + $categories = []; + + foreach ($modules as $module) { + if(!$module->hasAccess($user)) { + continue; + } + $usermodules[] = $module; + + $mdict = $module->getDataDictionary($this->lorisinstance); + $mname = $module->getName(); + + if(count($mdict) > 0) { + $categories[$mname] = []; + + + foreach($mdict as $cat) { + $categories[$mname][$cat->getName()] = $cat->getDescription(); + } + } + } + $this->modules = $usermodules; + $this->categories = $categories; + } } diff --git a/modules/datadict/php/datadictrow.class.inc b/modules/datadict/php/datadictrow.class.inc index ca9c4ebe702..bc08c493691 100644 --- a/modules/datadict/php/datadictrow.class.inc +++ b/modules/datadict/php/datadictrow.class.inc @@ -25,7 +25,7 @@ namespace LORIS\datadict; * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 * @link https://www.github.com/aces/Loris/ */ -class DataDictRow implements \LORIS\Data\DataInstance +class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\AccessibleResource { protected $DBRow; @@ -35,8 +35,9 @@ class DataDictRow implements \LORIS\Data\DataInstance * @param array $row The row (in the same format as \Database::pselectRow * returns */ - public function __construct(array $row) + public function __construct(\LORIS\Data\Dictionary\DictionaryItem $item, array $row) { + $this->item = $item; $this->DBRow = $row; } @@ -49,4 +50,8 @@ class DataDictRow implements \LORIS\Data\DataInstance { return $this->DBRow; } + + public function isAccessibleBy(\User $user) : bool { + return $this->item->isAccessibleBy($user); + } } diff --git a/modules/datadict/php/datadictrowprovisioner.class.inc b/modules/datadict/php/datadictrowprovisioner.class.inc index dbd24836808..58a825fb9cd 100644 --- a/modules/datadict/php/datadictrowprovisioner.class.inc +++ b/modules/datadict/php/datadictrowprovisioner.class.inc @@ -79,16 +79,18 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance $status = 'Empty'; } $dict[] = $this->getInstance( - [ - 'source_from' => $cat->getName(), - 'name' => $name, - 'source_field' => $item->getName(), - 'description' => $desc, - 'description_status' => $status, - 'datascope' => $item->getScope(), - 'type' => $item->getDataType(), - ] - ); + $item, + [ + 'module' => $module->getName(), + 'category' => $cat->getName(), + 'name' => $name, + 'source_field' => $item->getName(), + 'description' => $desc, + 'description_status' => $status, + 'datascope' => $item->getScope(), + 'type' => $item->getDataType(), + ] + ); } } } @@ -102,8 +104,8 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance * * @return \LORIS\Data\DataInstance An instance representing this row. */ - public function getInstance($row) : \LORIS\Data\DataInstance + public function getInstance(\LORIS\Data\Dictionary\DictionaryItem $item, array $row) : \LORIS\Data\DataInstance { - return new DataDictRow($row); + return new DataDictRow($item, $row); } } diff --git a/php/libraries/Module.class.inc b/php/libraries/Module.class.inc index 3e6cc2fae9d..488a787f3b1 100644 --- a/php/libraries/Module.class.inc +++ b/php/libraries/Module.class.inc @@ -348,7 +348,8 @@ abstract class Module extends \LORIS\Router\PrefixRouter } else { $_REQUEST['subtest'] = $pagename; } - $page->loadResources($user, $request); + + $page->loadResources($user, $request); if ($page->_hasAccess($user) !== true) { return (new \LORIS\Middleware\PageDecorationMiddleware( $user diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 4b046e0cb79..d16030fe3f4 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -569,13 +569,13 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument 'Candidate_age', 'Candidate Age (Months)', $scope, - new \LORIS\Data\Types\Duration(), + new \LORIS\Data\Types\Interval(), ), new DictionaryItem( 'Window_Difference', 'Window difference from test battery (days)', $scope, - new \LORIS\Data\Types\Duration(), + new \LORIS\Data\Types\Interval(), ), new DictionaryItem( 'Examiner', diff --git a/php/libraries/NDB_Page.class.inc b/php/libraries/NDB_Page.class.inc index 9cf0cc61682..60f48683ce1 100644 --- a/php/libraries/NDB_Page.class.inc +++ b/php/libraries/NDB_Page.class.inc @@ -941,4 +941,18 @@ class NDB_Page implements RequestHandlerInterface { return json_encode(['error' => 'Not implemented']); } + + /** + * This function can be overridden in a module's page to load the necessary + * resources to check the permissions of a user. + * + * @param User $user The user to load the resources for + * @param ServerRequestInterface $request The PSR15 Request being handled + * + * @return void + */ + public function loadResources( + \User $user, ServerRequestInterface $request + ) : void { + } } diff --git a/src/Data/Dictionary/DictionaryItem.php b/src/Data/Dictionary/DictionaryItem.php index d7305025bc4..8da9f751fa7 100644 --- a/src/Data/Dictionary/DictionaryItem.php +++ b/src/Data/Dictionary/DictionaryItem.php @@ -125,4 +125,21 @@ public function getScope() : Scope { public function getDataType() : \LORIS\Data\Type { return $this->typ; } + + /** + * The DictionaryItem instance implements the AccessibleResource + * interface in order to make it possible to restrict items per + * user. However, by default DictionaryItems are accessible by + * all users. In order to restrict access to certain items, a + * module would need to extend this class and override the + * isAccessibleBy method with its prefered business logic. + * + * @param \User $user The user whose access should be + * validated + * + * @return bool + */ + public function isAccessibleBy(\User $user): bool { + return true; + } } diff --git a/src/Data/Types/Duration.php b/src/Data/Types/Interval.php similarity index 100% rename from src/Data/Types/Duration.php rename to src/Data/Types/Interval.php From 2c99c1c495c07eee2390b530e77e2db2ec2b366d Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Thu, 16 Jul 2020 10:48:23 -0400 Subject: [PATCH 14/60] Add cardinality and imaging things --- jsx/FilterableDataTable.js | 14 +++---- modules/datadict/jsx/dataDictIndex.js | 14 +++++++ modules/datadict/php/datadict.class.inc | 9 ++++- .../php/datadictrowprovisioner.class.inc | 39 ++++++++++++++++++- modules/imaging_browser/php/module.class.inc | 27 +++++++++++++ .../NDB_BVL_Instrument_LINST.class.inc | 7 ++++ src/Data/Dictionary/DictionaryItem.php | 5 +++ 7 files changed, 104 insertions(+), 11 deletions(-) diff --git a/jsx/FilterableDataTable.js b/jsx/FilterableDataTable.js index f283c3c3253..7f340e07a51 100644 --- a/jsx/FilterableDataTable.js +++ b/jsx/FilterableDataTable.js @@ -17,18 +17,10 @@ import Filter from 'jsx/Filter'; * Deprecates Filter Form. */ class FilterableDataTable extends Component { -<<<<<<< HEAD /** * @constructor * @param {object} props - React Component properties */ -======= - /** - * x - * - * @param {object} props - x - */ ->>>>>>> WIP constructor(props) { super(props); this.state = { @@ -103,6 +95,12 @@ class FilterableDataTable extends Component { this.updateFilters({}); } + /** + * Returns filters which aren't in an invalid + * state + * + * @return {object} + */ validFilters() { let filters = {}; this.props.fields.forEach((field) => { diff --git a/modules/datadict/jsx/dataDictIndex.js b/modules/datadict/jsx/dataDictIndex.js index 608f316d3a0..87e8b4b3864 100644 --- a/modules/datadict/jsx/dataDictIndex.js +++ b/modules/datadict/jsx/dataDictIndex.js @@ -302,6 +302,20 @@ class DataDictIndex extends Component { type: 'text', }, }, + { + label: 'Data Cardinality', + show: true, + filter: { + name: 'cardinality', + type: 'select', + options: { + 'unique': 'Unique', + 'single': 'Single', + 'optional': 'Optional', + 'many': 'Many', + }, + }, + }, ]; return ( lorisinstance); + return new DataDictRowProvisioner($this->lorisinstance, $this->categoryitems); } /** @@ -160,21 +160,26 @@ class Datadict extends \DataFrameworkMenu $dict = []; $categories = []; + $this->categoryitems = []; foreach ($modules as $module) { if(!$module->hasAccess($user)) { continue; } - $usermodules[] = $module; $mdict = $module->getDataDictionary($this->lorisinstance); $mname = $module->getName(); if(count($mdict) > 0) { + $usermodules[] = $module; $categories[$mname] = []; foreach($mdict as $cat) { $categories[$mname][$cat->getName()] = $cat->getDescription(); + $this->categoryitems[] = [ + 'Module' => $module, + 'Category' => $cat, + ]; } } } diff --git a/modules/datadict/php/datadictrowprovisioner.class.inc b/modules/datadict/php/datadictrowprovisioner.class.inc index 58a825fb9cd..4dff4cd6c18 100644 --- a/modules/datadict/php/datadictrowprovisioner.class.inc +++ b/modules/datadict/php/datadictrowprovisioner.class.inc @@ -37,9 +37,10 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance * @param \LORIS\LorisInstance $loris The Loris instance whose * dictionary should be retrieved */ - function __construct(\LORIS\LorisInstance $loris) + function __construct(\LORIS\LorisInstance $loris, array $items) { $this->loris = $loris; + $this->items = $items; } /** @@ -61,6 +62,40 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance "Name" ); + foreach ($this->items as $row) { + $module = $row['Module']; + $cat = $row['Category']; + foreach ($cat->getItems() as $item) { + $name = $cat->getName() . '_' . $item->getName(); + $desc = ''; + $status = 'Unchanged'; + if (isset($overrides[$name])) { + $desc = $overrides[$name]['Description']; + $status = 'Modified'; + } else { + $desc = $item->getDescription(); + } + + if ($desc == '') { + $status = 'Empty'; + } + $dict[] = $this->getInstance( + $item, + [ + 'module' => $module->getName(), + 'category' => $cat->getName(), + 'name' => $name, + 'source_field' => $item->getName(), + 'description' => $desc, + 'description_status' => $status, + 'datascope' => $item->getScope(), + 'type' => $item->getDataType(), + 'cardinality' => $item->getCardinality(), + ] + ); + } + } + /* foreach ($modules as $module) { $mdict = $module->getDataDictionary($this->loris); foreach($mdict as $cat) { @@ -89,11 +124,13 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance 'description_status' => $status, 'datascope' => $item->getScope(), 'type' => $item->getDataType(), + 'cardinality' => $item->getCardinality(), ] ); } } } + */ return new \ArrayIterator($dict); } /** diff --git a/modules/imaging_browser/php/module.class.inc b/modules/imaging_browser/php/module.class.inc index 64df2b2c8b3..80f4fee0236 100644 --- a/modules/imaging_browser/php/module.class.inc +++ b/modules/imaging_browser/php/module.class.inc @@ -125,6 +125,33 @@ class Module extends \Module } return []; } + public function getDataDictionary(\LORIS\LorisInstance $loris) : iterable + { + $scope = new Scope(Scope::Session); + + $images = new \LORIS\Data\Dictionary\Category("Images", "Image Acquisitions"); + + $items = []; + $scantypes = \Utility::getScanTypeList(); + foreach ($scantypes as $ScanType) { + $items[] = new DictionaryItem( + $ScanType, + "$ScanType acquisition location", + $scope, + new \LORIS\Data\Types\URI(), + new Cardinality(Cardinality::Many), + ); + // TODO: Investigate adding a file scope instead of having this apply + // on a session scope with a Many cardinality. + $items[] = new DictionaryItem( + $ScanType . "_QCStatus", + "Quality Control status for $ScanType acquisition", + $scope, + new \LORIS\Data\Types\Enumeration("Pass", "Fail"), + new Cardinality(Cardinality::Many), + ); + } + $images = $images->withItems($items); /** * {@inheritDoc} diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index d16030fe3f4..dcbe4e05e45 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -564,18 +564,21 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument 'Date of Administration', $scope, new \LORIS\Data\Types\DateType(), + new Cardinality(Cardinality::Single), ), new DictionaryItem( 'Candidate_age', 'Candidate Age (Months)', $scope, new \LORIS\Data\Types\Interval(), + new Cardinality(Cardinality::Single), ), new DictionaryItem( 'Window_Difference', 'Window difference from test battery (days)', $scope, new \LORIS\Data\Types\Interval(), + new Cardinality(Cardinality::Single), ), new DictionaryItem( 'Examiner', @@ -583,6 +586,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $scope, // Should this be an enum of examiners? new StringType(255), + new Cardinality(Cardinality::Single), ), ] ); @@ -659,6 +663,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $pieces[2], $scope, new StringType(255), + new Cardinality(Cardinality::Single), ); } $this->dictionary[] = new DictionaryItem( @@ -690,6 +695,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $pieces[2], $scope, new StringType(), + new Cardinality(Cardinality::Single), ); } $this->dictionary[] = new DictionaryItem( @@ -724,6 +730,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $pieces[2], $scope, new DateType(), + new Cardinality(Cardinality::Single), ); $this->dictionary[] = new DictionaryItem( diff --git a/src/Data/Dictionary/DictionaryItem.php b/src/Data/Dictionary/DictionaryItem.php index 8da9f751fa7..557110ec594 100644 --- a/src/Data/Dictionary/DictionaryItem.php +++ b/src/Data/Dictionary/DictionaryItem.php @@ -3,6 +3,7 @@ namespace LORIS\Data\Dictionary; use \LORIS\Data\Scope; use \LORIS\Data\Type; +use \LORIS\Data\Cardinality; use \LORIS\Data\Scope; use \LORIS\Data\Type; @@ -126,6 +127,10 @@ public function getDataType() : \LORIS\Data\Type { return $this->typ; } + public function getCardinality() : \LORIS\Data\Cardinality { + return $this->cardinality; + } + /** * The DictionaryItem instance implements the AccessibleResource * interface in order to make it possible to restrict items per From e24d64c32221c7e24ed740202b0ccf223619b143 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Thu, 16 Jul 2020 13:44:36 -0400 Subject: [PATCH 15/60] add missing files --- src/Data/Cardinality.php | 1 - src/Data/Filters/AccessibleResourceFilter.php | 1 + src/Data/Types/TimeType.php | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Data/Cardinality.php b/src/Data/Cardinality.php index 1e07fd6380e..05bf6c3e34c 100644 --- a/src/Data/Cardinality.php +++ b/src/Data/Cardinality.php @@ -1,6 +1,5 @@ isAccessibleBy($user); } } + diff --git a/src/Data/Types/TimeType.php b/src/Data/Types/TimeType.php index 005150023e3..c4a89283f00 100644 --- a/src/Data/Types/TimeType.php +++ b/src/Data/Types/TimeType.php @@ -1,6 +1,5 @@ Date: Wed, 22 Jul 2020 13:24:47 -0400 Subject: [PATCH 16/60] Remove category from field name --- modules/datadict/jsx/dataDictIndex.js | 21 +++------ .../php/datadictrowprovisioner.class.inc | 43 +++---------------- 2 files changed, 11 insertions(+), 53 deletions(-) diff --git a/modules/datadict/jsx/dataDictIndex.js b/modules/datadict/jsx/dataDictIndex.js index 87e8b4b3864..e581c19dba8 100644 --- a/modules/datadict/jsx/dataDictIndex.js +++ b/modules/datadict/jsx/dataDictIndex.js @@ -65,8 +65,6 @@ class DataDictIndex extends Component { } else { this.setState({moduleFilter: ''}); } - - console.log(filter); } editSwal(row) { @@ -87,7 +85,7 @@ class DataDictIndex extends Component { return; } - const url = loris.BaseURL + '/datadict/fields/' + encodeURI(row.Name); + const url = loris.BaseURL + '/datadict/fields/' + encodeURI(row['Field Name']); // The fetch happens asyncronously, which means that the // swal closes before it returns. We find the index that @@ -97,7 +95,7 @@ class DataDictIndex extends Component { let odesc; let ostat; for (i = 0; i < this.state.data.Data.length; i++) { - if (this.state.data.Data[i][1] == row.Name) { + if (this.state.data.Data[i][2] == row['Field Name']) { // Store the original values in case the fetch // fails and we need to restore them. odesc = this.state.data.Data[i][3]; @@ -113,6 +111,7 @@ class DataDictIndex extends Component { break; } } + fetch(url, { method: 'PUT', credentials: 'same-origin', @@ -237,7 +236,7 @@ class DataDictIndex extends Component { }, { label: 'Category', - show: true, + show: false, filter: { name: 'Source From', type: 'multiselect', @@ -245,18 +244,10 @@ class DataDictIndex extends Component { }, }, { - label: 'Name', - show: false, - filter: { - name: 'Name', - type: 'text', - }, - }, - { - label: 'Source Field', + label: 'Field Name', show: true, filter: { - name: 'Source Field', + name: 'Name', type: 'text', }, }, diff --git a/modules/datadict/php/datadictrowprovisioner.class.inc b/modules/datadict/php/datadictrowprovisioner.class.inc index 4dff4cd6c18..a4a12506f0b 100644 --- a/modules/datadict/php/datadictrowprovisioner.class.inc +++ b/modules/datadict/php/datadictrowprovisioner.class.inc @@ -66,7 +66,7 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance $module = $row['Module']; $cat = $row['Category']; foreach ($cat->getItems() as $item) { - $name = $cat->getName() . '_' . $item->getName(); + $name = $item->getName(); $desc = ''; $status = 'Unchanged'; if (isset($overrides[$name])) { @@ -84,8 +84,11 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance [ 'module' => $module->getName(), 'category' => $cat->getName(), - 'name' => $name, + /* + 'fieldname' => $name, 'source_field' => $item->getName(), + */ + 'field' => $item->getName(), 'description' => $desc, 'description_status' => $status, 'datascope' => $item->getScope(), @@ -95,42 +98,6 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance ); } } - /* - foreach ($modules as $module) { - $mdict = $module->getDataDictionary($this->loris); - foreach($mdict as $cat) { - foreach($cat->getItems() as $item) { - $name = $cat->getName() . '_' . $item->getName(); - $desc = ''; - $status = 'Unchanged'; - if (isset($overrides[$name])) { - $desc = $overrides[$name]['Description']; - $status = 'Modified'; - } else { - $desc = $item->getDescription(); - } - - if ($desc == '') { - $status = 'Empty'; - } - $dict[] = $this->getInstance( - $item, - [ - 'module' => $module->getName(), - 'category' => $cat->getName(), - 'name' => $name, - 'source_field' => $item->getName(), - 'description' => $desc, - 'description_status' => $status, - 'datascope' => $item->getScope(), - 'type' => $item->getDataType(), - 'cardinality' => $item->getCardinality(), - ] - ); - } - } - } - */ return new \ArrayIterator($dict); } /** From c673653e73bf7af38337d3ee85d4b1afea434eb1 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 3 Aug 2020 14:02:38 -0400 Subject: [PATCH 17/60] fix build after rebase --- modules/datadict/jsx/dataDictIndex.js | 27 +++++++++++++++++-- .../php/datadictrowprovisioner.class.inc | 8 +++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/modules/datadict/jsx/dataDictIndex.js b/modules/datadict/jsx/dataDictIndex.js index e581c19dba8..57040d584b5 100644 --- a/modules/datadict/jsx/dataDictIndex.js +++ b/modules/datadict/jsx/dataDictIndex.js @@ -59,6 +59,12 @@ class DataDictIndex extends Component { this.fetchData(); } + /** + * Update the filter to dynamically change the options in the + * 'Category' dropdown based on the selected module. + * + * @param {object} filter - The current filter state + */ updateFilter(filter) { if (filter.Module) { this.setState({moduleFilter: filter.Module.value}); @@ -67,6 +73,13 @@ class DataDictIndex extends Component { } } + /** + * Display a sweetalert popup to modify the row + * + * @param {object} row - The row being modified + * + * @return {function} callback function for react to activate swal + */ editSwal(row) { return () => { swal.fire({ @@ -85,7 +98,9 @@ class DataDictIndex extends Component { return; } - const url = loris.BaseURL + '/datadict/fields/' + encodeURI(row['Field Name']); + const url = this.props.BaseURL + + '/datadict/fields/' + + encodeURI(row['Field Name']); // The fetch happens asyncronously, which means that the // swal closes before it returns. We find the index that @@ -205,7 +220,6 @@ class DataDictIndex extends Component { ); } - return {cell} {edited} {editIcon} ; } /** @@ -307,6 +321,15 @@ class DataDictIndex extends Component { }, }, }, + { + // We may or may not have an 8th column depending + // on type, which we need for formatting other columns. + // We don't show or display a filter because it's only + // valid for some data types. + label: 'Field Options', + show: false, + filter: null, + }, ]; return ( $module->getName(), 'category' => $cat->getName(), - /* - 'fieldname' => $name, - 'source_field' => $item->getName(), - */ 'field' => $item->getName(), 'description' => $desc, 'description_status' => $status, @@ -110,6 +106,10 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance */ public function getInstance(\LORIS\Data\Dictionary\DictionaryItem $item, array $row) : \LORIS\Data\DataInstance { + $itype = $item->getDataType(); + if ($itype instanceof \LORIS\Data\Types\Enumeration) { + $row['options'] = $itype->getOptions(); + } return new DataDictRow($item, $row); } } From 75f55d936dcf882d1e58ab0811c7cc743baf2df3 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 3 Aug 2020 14:50:06 -0400 Subject: [PATCH 18/60] Move fieldOptions to new endpoint --- modules/datadict/php/categories.class.inc | 78 +++++++++++++++++++++++ modules/datadict/php/datadict.class.inc | 36 ++--------- modules/datadict/php/module.class.inc | 46 +++++++++++++ 3 files changed, 131 insertions(+), 29 deletions(-) create mode 100644 modules/datadict/php/categories.class.inc diff --git a/modules/datadict/php/categories.class.inc b/modules/datadict/php/categories.class.inc new file mode 100644 index 00000000000..cb00597eea3 --- /dev/null +++ b/modules/datadict/php/categories.class.inc @@ -0,0 +1,78 @@ +getQueryParams(); + if (isset($queryparams['module'])) { + return $this->moduleDictionary($request, $queryparams['module']); + } + + $modulesandcats = $this->Module->getUserModuleCategories( + $request->getAttribute("user"), + $request->getAttribute("loris") + ); + + $modulesassoc = []; + + foreach($modulesandcats['Modules'] as $module) { + $modulesassoc[$module->getName()] = $module->getLongName(); + } + + return new \LORIS\Http\Response\JSON\OK( [ + 'modules' => $modulesassoc, + 'categories' => $modulesandcats['Categories'], + ]); + } + + public function moduleDictionary(ServerRequestInterface $request, string $modulename) : ResponseInterface + { + $loris = $this->lorisinstance; + $module = null; + foreach ($loris->getActiveModules() as $m) { + if ($m->getName() == $modulename) { + $module = $m; + break; + } + } + + if($module === null) { + return new \LORIS\Http\Response\JSON\NotFound('module not found'); + } + $mdict = $module->getDataDictionary($loris); + $dict = []; + /* + [ + 'module' => $module->getName(), + 'category' => $cat->getName(), + 'field' => $item->getName(), + 'description' => $desc, + 'description_status' => $status, + 'datascope' => $item->getScope(), + 'type' => $item->getDataType(), + 'cardinality' => $item->getCardinality(), + ] + ); + } + */ + + foreach ($mdict as $cat) { + $catdict = []; + + foreach($cat->getItems() as $item) { + $catdict[$item->getName()] = [ + 'description' => $item->getDescription(), + 'scope' => $item->getScope(), + 'type' => $item->getDataType(), + 'cardinality' => $item->getCardinality(), + ]; + } + $dict[$cat->getName()] = $catdict; + } + return new \LORIS\Http\Response\JSON\OK($dict); +} +} diff --git a/modules/datadict/php/datadict.class.inc b/modules/datadict/php/datadict.class.inc index bebf717da98..30a370f0993 100644 --- a/modules/datadict/php/datadict.class.inc +++ b/modules/datadict/php/datadict.class.inc @@ -155,35 +155,13 @@ class Datadict extends \DataFrameworkMenu \User $user, ServerRequestInterface $request ) : void { $this->lorisinstance = $request->getAttribute("loris"); - $modules = $this->lorisinstance->getActiveModules(); - $usermodules = []; - $dict = []; - $categories = []; - - $this->categoryitems = []; - foreach ($modules as $module) { - if(!$module->hasAccess($user)) { - continue; - } - - $mdict = $module->getDataDictionary($this->lorisinstance); - $mname = $module->getName(); - - if(count($mdict) > 0) { - $usermodules[] = $module; - $categories[$mname] = []; - + $modulesandcats = $this->Module->getUserModuleCategories( + $user, + $this->lorisinstance, + ); - foreach($mdict as $cat) { - $categories[$mname][$cat->getName()] = $cat->getDescription(); - $this->categoryitems[] = [ - 'Module' => $module, - 'Category' => $cat, - ]; - } - } - } - $this->modules = $usermodules; - $this->categories = $categories; + $this->categoryitems = $modulesandcats['CategoryItems']; + $this->modules = $modulesandcats['Modules']; + $this->categories = $modulesandcats['Categories']; } } diff --git a/modules/datadict/php/module.class.inc b/modules/datadict/php/module.class.inc index 87f902bf4c9..d477705c908 100644 --- a/modules/datadict/php/module.class.inc +++ b/modules/datadict/php/module.class.inc @@ -57,4 +57,50 @@ class Module extends \Module { return "Data Dictionary"; } + + /** + * Return a list of modules and categories that this user + * has access to. + * + * This should not be used outside of this module, it's only + * to store shared code between different endpoints + * + * @return array + */ + public function getUserModuleCategories(\User $user, \LORIS\LorisInstance $loris) : array + { + $modules = $loris->getActiveModules(); + $usermodules = []; + $dict = []; + $categories = []; + + $categoryitems = []; + foreach ($modules as $module) { + if(!$module->hasAccess($user)) { + continue; + } + + $mdict = $module->getDataDictionary($this->lorisinstance); + $mname = $module->getName(); + + if(count($mdict) > 0) { + $usermodules[] = $module; + $categories[$mname] = []; + + + foreach($mdict as $cat) { + $categories[$mname][$cat->getName()] = $cat->getDescription(); + $categoryitems[] = [ + 'Module' => $module, + 'Category' => $cat, + ]; + } + } + } + return [ + 'Modules' => $usermodules, + 'Categories' => $categories, + 'CategoryItems' => $categoryitems, + ]; + } } From 64cc8cf9f97406ba7ef960a68c3bb4f59c721366 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 5 Aug 2020 13:47:19 -0400 Subject: [PATCH 19/60] Share more code between endpoints --- modules/datadict/php/categories.class.inc | 57 +++++++++------------- modules/datadict/php/datadictrow.class.inc | 21 ++++++++ modules/datadict/php/module.class.inc | 5 +- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/modules/datadict/php/categories.class.inc b/modules/datadict/php/categories.class.inc index cb00597eea3..b9bce0da8a1 100644 --- a/modules/datadict/php/categories.class.inc +++ b/modules/datadict/php/categories.class.inc @@ -8,13 +8,14 @@ class Categories extends \NDB_Page { public function handle(ServerRequestInterface $request) : ResponseInterface { $queryparams = $request->getQueryParams(); + $user = $request->getAttribute("user"); if (isset($queryparams['module'])) { - return $this->moduleDictionary($request, $queryparams['module']); + return $this->moduleDictionary($request, $user, $queryparams['module']); } $modulesandcats = $this->Module->getUserModuleCategories( - $request->getAttribute("user"), - $request->getAttribute("loris") + $user, + $request->getAttribute("loris"), ); $modulesassoc = []; @@ -29,10 +30,10 @@ class Categories extends \NDB_Page { ]); } - public function moduleDictionary(ServerRequestInterface $request, string $modulename) : ResponseInterface + public function moduleDictionary(ServerRequestInterface $request, \User $user, string $modulename) : ResponseInterface { + $loris = $this->lorisinstance; - $module = null; foreach ($loris->getActiveModules() as $m) { if ($m->getName() == $modulename) { $module = $m; @@ -43,36 +44,26 @@ class Categories extends \NDB_Page { if($module === null) { return new \LORIS\Http\Response\JSON\NotFound('module not found'); } - $mdict = $module->getDataDictionary($loris); - $dict = []; - /* - [ - 'module' => $module->getName(), - 'category' => $cat->getName(), - 'field' => $item->getName(), - 'description' => $desc, - 'description_status' => $status, - 'datascope' => $item->getScope(), - 'type' => $item->getDataType(), - 'cardinality' => $item->getCardinality(), - ] - ); - } - */ - foreach ($mdict as $cat) { - $catdict = []; + $data = $this->Module->getUserModuleCategories($user, $loris, $modulename); + $prov = (new DataDictRowProvisioner($loris, $data['CategoryItems'])) + ->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); + - foreach($cat->getItems() as $item) { - $catdict[$item->getName()] = [ - 'description' => $item->getDescription(), - 'scope' => $item->getScope(), - 'type' => $item->getDataType(), - 'cardinality' => $item->getCardinality(), - ]; + $organized = []; + foreach ($prov->execute($user) as $row) { + $cat = $row->getCategory(); + if (!isset($organized[$cat])) { + $organized[$cat] = []; + } + $fieldname = $row->getFieldName(); + $organized[$cat][$fieldname] = [ + 'description' => $row->getDescription(), + 'scope' => $row->getScope(), + 'type' => $row->getDataType(), + 'cardinality' => $row->getCardinality(), + ]; } - $dict[$cat->getName()] = $catdict; + return new \LORIS\Http\Response\JSON\OK($organized); } - return new \LORIS\Http\Response\JSON\OK($dict); -} } diff --git a/modules/datadict/php/datadictrow.class.inc b/modules/datadict/php/datadictrow.class.inc index bc08c493691..f111ed98b81 100644 --- a/modules/datadict/php/datadictrow.class.inc +++ b/modules/datadict/php/datadictrow.class.inc @@ -54,4 +54,25 @@ class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\Acce public function isAccessibleBy(\User $user) : bool { return $this->item->isAccessibleBy($user); } + + public function getCategory() : string { + return $this->DBRow['category']; + } + + public function getFieldName() : string { + return $this->DBRow['field']; + } + public function getDescription() : string { + return $this->DBRow['description']; + } + public function getScope() : \LORIS\Data\Scope { + return $this->DBRow['datascope']; + } + public function getDataType() : \LORIS\Data\Type{ + return $this->DBRow['type']; + } + public function getCardinality() : \LORIS\Data\Cardinality { + return $this->DBRow['cardinality']; + } + } diff --git a/modules/datadict/php/module.class.inc b/modules/datadict/php/module.class.inc index d477705c908..1c6c1a7b73f 100644 --- a/modules/datadict/php/module.class.inc +++ b/modules/datadict/php/module.class.inc @@ -67,7 +67,7 @@ class Module extends \Module * * @return array */ - public function getUserModuleCategories(\User $user, \LORIS\LorisInstance $loris) : array + public function getUserModuleCategories(\User $user, \LORIS\LorisInstance $loris, ?string $formodule=null) : array { $modules = $loris->getActiveModules(); $usermodules = []; @@ -76,6 +76,9 @@ class Module extends \Module $categoryitems = []; foreach ($modules as $module) { + if ($formodule !== null && $module->getName() !== $formodule) { + continue; + } if(!$module->hasAccess($user)) { continue; } From 6dd8e6bfddc8183081306f22ef6c0d523b1f0ec9 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Thu, 6 Aug 2020 15:34:49 -0400 Subject: [PATCH 20/60] Add options for enum --- modules/datadict/php/categories.class.inc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/datadict/php/categories.class.inc b/modules/datadict/php/categories.class.inc index b9bce0da8a1..cfda0894f19 100644 --- a/modules/datadict/php/categories.class.inc +++ b/modules/datadict/php/categories.class.inc @@ -57,12 +57,16 @@ class Categories extends \NDB_Page { $organized[$cat] = []; } $fieldname = $row->getFieldName(); + $datatype = $row->getDataType(); $organized[$cat][$fieldname] = [ 'description' => $row->getDescription(), 'scope' => $row->getScope(), 'type' => $row->getDataType(), 'cardinality' => $row->getCardinality(), ]; + if ($datatype instanceof \LORIS\Data\Types\Enumeration) { + $organized[$cat][$fieldname]['options'] = $datatype->getOptions(); + } } return new \LORIS\Http\Response\JSON\OK($organized); } From a77cef89a7edfd694802080929747ad0500e42fc Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 24 Aug 2020 11:19:07 -0400 Subject: [PATCH 21/60] Move to new module name --- modules/dictionary/.gitignore | 2 + modules/dictionary/README.md | 51 +++ modules/dictionary/ajax/UpdateDataDict.php | 65 ++++ modules/dictionary/help/datadict.md | 8 + modules/dictionary/js/datadict_helper.js | 25 ++ modules/dictionary/jsx/dataDictIndex.js | 345 ++++++++++++++++++ modules/dictionary/php/categories.class.inc | 73 ++++ modules/dictionary/php/datadictrow.class.inc | 78 ++++ .../php/datadictrowprovisioner.class.inc | 115 ++++++ modules/dictionary/php/dictionary.class.inc | 143 ++++++++ modules/dictionary/php/fields.class.inc | 128 +++++++ modules/dictionary/php/module.class.inc | 109 ++++++ modules/dictionary/test/TestPlan.md | 39 ++ modules/dictionary/test/datadictTest.php | 117 ++++++ 14 files changed, 1298 insertions(+) create mode 100644 modules/dictionary/.gitignore create mode 100644 modules/dictionary/README.md create mode 100644 modules/dictionary/ajax/UpdateDataDict.php create mode 100644 modules/dictionary/help/datadict.md create mode 100644 modules/dictionary/js/datadict_helper.js create mode 100644 modules/dictionary/jsx/dataDictIndex.js create mode 100644 modules/dictionary/php/categories.class.inc create mode 100644 modules/dictionary/php/datadictrow.class.inc create mode 100644 modules/dictionary/php/datadictrowprovisioner.class.inc create mode 100644 modules/dictionary/php/dictionary.class.inc create mode 100644 modules/dictionary/php/fields.class.inc create mode 100644 modules/dictionary/php/module.class.inc create mode 100644 modules/dictionary/test/TestPlan.md create mode 100644 modules/dictionary/test/datadictTest.php diff --git a/modules/dictionary/.gitignore b/modules/dictionary/.gitignore new file mode 100644 index 00000000000..076131bc368 --- /dev/null +++ b/modules/dictionary/.gitignore @@ -0,0 +1,2 @@ +js/columnFormatter.js +js/dataDictIndex.js diff --git a/modules/dictionary/README.md b/modules/dictionary/README.md new file mode 100644 index 00000000000..2c3e8d5a4dd --- /dev/null +++ b/modules/dictionary/README.md @@ -0,0 +1,51 @@ +# Data Dictionary + +## Purpose + +The data dictionary module is used for viewing and modifying the +LORIS data dictionary descriptions. + +## Scope + +The module displays and manages the data dictionary for fields +already stored in the LORIS `parameter_type` table. This table is +generally autopopulated and provides a dictionary for imaging headers +(populated by the imaging pipeline) and behavioural instrument +(populated by the `lorisform_parser.php` script). + +It does not provide a way to enter new fields into the data dictionary, +or describe the data dictionary for arbitrary SQL fields which are +not stored in the `parameter_type` table, such as instrument flags +or candidate/session data. + +## Permission + +The `data_dict_view` permission allows the user to view the LORIS +data dictionary. + +The `data_dict_edit` permission allows the user to both view, and +modify the description of, rows in the data dictionary. + +## Configuration + +The `parameter_type` table must be populated to use this module. +The data comes from two sources: + +1. The LORIS imaging pipeline scripts, which must be setup separately +2. The `tools/data_dictionary_builder.php` script, which loads the + behavioural data dictionary based on the `ip_output.txt` file created + by `tools/lorisform_parser.php` + +## Interactions with LORIS + +The content for the data dictionary module comes from the +`parameter_type` table. + +Modified entries are saved in the separate `parameter_type_override` +table, and the "data dictionary" used by LORIS is the coalesce of +the two tables, with `parameter_type_override` taking priority. +This allows the `parameter_type` table itself to be regenerated +when instruments change without losing the user modified values. + +The data query module uses the customized data dictionary descriptions +from this module. diff --git a/modules/dictionary/ajax/UpdateDataDict.php b/modules/dictionary/ajax/UpdateDataDict.php new file mode 100644 index 00000000000..063df666c69 --- /dev/null +++ b/modules/dictionary/ajax/UpdateDataDict.php @@ -0,0 +1,65 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ + +$user =& User::singleton(); +if (!$user->hasPermission('data_dict_edit')) { + header("HTTP/1.1 403 Forbidden"); + exit; +} + +set_include_path(get_include_path().":../project/libraries:../php/libraries:"); +ini_set('default_charset', 'utf-8'); +/* This is used by the data dictionary page to + * update the column descriptions on the fly */ +require_once "Database.class.inc"; +require_once 'NDB_Config.class.inc'; +require_once 'NDB_Client.class.inc'; +$config =& NDB_Config::singleton(); +$client = new NDB_Client(); +$client->initialize(); + + +list($name,$extra) = explode("___", $_REQUEST['fieldname']); + +$description = trim($_REQUEST['description']); + +// create user object +$user =& User::singleton(); + +if ($user->hasPermission('data_dict_edit')) { //if user has edit permission + + $native_description = $DB->pselectOne( + "SELECT Description FROM parameter_type WHERE Name = :v_name", + ["v_name" => $name] + ); + + if ($description != $native_description) { + if (empty($description)) { + $description = ' '; + } + $DB->replace( + 'parameter_type_override', + [ + 'Description' => $description, + 'Name' => $name, + ] + ); + } else { + $DB->delete( + 'parameter_type_override', + ['Name' => $name] + ); + } +} + + diff --git a/modules/dictionary/help/datadict.md b/modules/dictionary/help/datadict.md new file mode 100644 index 00000000000..5c3f5a5e4c0 --- /dev/null +++ b/modules/dictionary/help/datadict.md @@ -0,0 +1,8 @@ +# Data Dictionary + +This module houses definitions or descriptions for the data fields in instrument forms loaded in your LORIS. + +Use the *Selection Filter* section to search for specific fields. Your results will be displayed in the data table below. + +Note that you can directly edit any cell in the *Description* column - this should be done with caution. +When a field description is modified, the new description is used in the Data Query Tool (after it is refreshed), by users who are selecting data fields to analyze or download. The modified field description will not be visible in the instrument form into which a user or survey participant enters data. diff --git a/modules/dictionary/js/datadict_helper.js b/modules/dictionary/js/datadict_helper.js new file mode 100644 index 00000000000..192eaf88db9 --- /dev/null +++ b/modules/dictionary/js/datadict_helper.js @@ -0,0 +1,25 @@ +function save() { + + $('.description'). + bind('blur',function(event){ + event.stopImmediatePropagation(); + id = event.target.id; + value = $("#" + id) .text(); + // sendRemoteDataQuery("query_gui_data_loader.php?mode=loadQuery&action="+action+"&qid="+qid); + $.get(loris.BaseURL + "/datadict/ajax/UpdateDataDict.php?fieldname=" + id + "&description=" + value, function(data) { + } + ); + } + ).keypress(function(e) { + if(e.which === 13) { // Determine if the user pressed the enter button + $(this).blur(); + } + + }); +}; + + +$(function(){ + save(); +}); + diff --git a/modules/dictionary/jsx/dataDictIndex.js b/modules/dictionary/jsx/dataDictIndex.js new file mode 100644 index 00000000000..d5d11da0acb --- /dev/null +++ b/modules/dictionary/jsx/dataDictIndex.js @@ -0,0 +1,345 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Loader from 'Loader'; +import FilterableDataTable from 'FilterableDataTable'; +import swal from 'sweetalert2'; + +/** + * Data Dictionary Page. + * + * Serves as an entry-point to the module, rendering the whole react + * component page on load. + * + * Renders Data Dictionary main page consisting of FilterTable and + * DataTable components. + * + * @author Liza Levitis + * @version 1.0.0 + * + */ +class DataDictIndex extends Component { + /** + * @constructor + * @param {object} props - React Component properties + */ + constructor(props) { + super(props); + + this.state = { + data: {}, + error: false, + isLoaded: false, + moduleFilter: '', + }; + + this.fetchData = this.fetchData.bind(this); + this.formatColumn = this.formatColumn.bind(this); + this.editSwal = this.editSwal.bind(this); + this.updateFilter = this.updateFilter.bind(this); + } + + /** + * Called by React when the component has been rendered on the page. + */ + componentDidMount() { + this.fetchData() + .then( () => this.setState({isLoaded: true})); + } + + /** + * Update the filter to dynamically change the options in the + * 'Category' dropdown based on the selected module. + * + * @param {object} filter - The current filter state + */ + updateFilter(filter) { + if (filter.Module) { + this.setState({moduleFilter: filter.Module.value}); + } else { + this.setState({moduleFilter: ''}); + } + } + + /** + * Display a sweetalert popup to modify the row + * + * @param {object} row - The row being modified + * + * @return {function} callback function for react to activate swal + */ + editSwal(row) { + return () => { + swal.fire({ + title: 'Edit Description', + input: 'text', + inputValue: row.Description, + confirmButtonText: 'Modify', + showCancelButton: true, + inputValidator: (value) => { + if (!value) { + return 'Missing description'; + } + }, + }).then((result) => { + if (!result.value) { + return; + } + + const url = this.props.BaseURL + + '/datadict/fields/' + + encodeURI(row['Field Name']); + + // The fetch happens asyncronously, which means that the + // swal closes before it returns. We find the index that + // was being updated and aggressively update it, then + // re-update or reset it when the PUT request returns. + let i; + let odesc; + let ostat; + for (i = 0; i < this.state.data.Data.length; i++) { + if (this.state.data.Data[i][2] == row['Field Name']) { + // Store the original values in case the fetch + // fails and we need to restore them. + odesc = this.state.data.Data[i][3]; + ostat = this.state.data.Data[i][4]; + + // Aggressively update the state and assume + // it's been modified. + this.state.data.Data[i][3] = result.value; + this.state.data.Data[i][4] = 'Modified'; + + // Force a re-render + this.setState({state: this.state}); + break; + } + } + + fetch(url, { + method: 'PUT', + credentials: 'same-origin', + cache: 'no-cache', + body: result.value, + }).then((response) => { + if (!response.ok) { + // The response wasn't in the 200-299 range, + // so revert the update we did above and + // force a re-render. + this.state.data.Data[i][3] = odesc; + this.state.data.Data[i][4] = ostat; + + // Force a re-render + this.setState({state: this.state}); + return; + } + + // The response to the PUT request said we're + // good, but it's possible the status was changed + // back to the original. So update the status + // based on what the response said the value was. + this.state.data.Data[i][4] = response.headers.get('X-StatusDesc'); + this.setState({state: this.state}); + }).catch(() => { + // Something went wrong, restore the original + // status and description + this.state.data.Data[i][3] = odesc; + this.state.data.Data[i][4] = ostat; + + // Force a re-render + this.setState({state: this.state}); + }); + }); + }; + } + + /** + * Retrive data from the provided URL and save it in state + * + * @return {object} + */ + fetchData() { + return fetch(this.props.dataURL, {credentials: 'same-origin'}) + .then((resp) => resp.json()) + .then((data) => { + this.setState({data}); + }) + .catch((error) => { + this.setState({error: true}); + console.error(error); + }); + } + + /** + * Modify behaviour of specified column cells in the Data Table component + * + * @param {string} column - column name + * @param {string} cell - cell content + * @param {array} rowData - array of cell contents for a specific row + * @param {array} rowHeaders - array of table headers (column names) + * + * @return {*} a formated table cell for a given column + */ + formatColumn(column, cell, rowData, rowHeaders) { + const hasEditPermission = loris.userHasPermission('data_dict_edit'); + let editIcon = ''; + let edited=''; + switch (column) { + case 'Description': + if (hasEditPermission) { + editIcon = ( + ); + } + + if (rowData['Description Status'] == 'Modified') { + edited = (edited); + } + return {cell} + {edited} {editIcon} + ; + case 'Data Type': + if (cell == 'enumeration') { + cell = rowData['Field Options'].join(';'); + } + return {cell}; + default: + return {cell}; + } + } + + /** + * Renders the React component. + * + * @return {JSX} - React markup for the component + */ + render() { + if (this.state.error) { + return

An error occured while loading the page.

; + } + + // Waiting for async data to load + if (!this.state.isLoaded) { + return ; + } + + let options = this.state.data.fieldOptions; + let fields = [ + { + label: 'Module', + show: true, + filter: { + name: 'Module', + type: 'select', + options: options.modules, + }, + }, + { + label: 'Category', + show: false, + filter: { + name: 'Category', + type: 'select', + options: this.state.moduleFilter == '' + ? {} + : options.categories[this.state.moduleFilter], + }, + }, + { + label: 'Field Name', + show: true, + filter: { + name: 'Name', + type: 'text', + }, + }, + { + label: 'Description', + show: true, + filter: { + name: 'Description', + type: 'text', + }, + }, + { + label: 'Description Status', + show: false, + filter: { + name: 'DescriptionStatus', + type: 'select', + options: { + 'empty': 'Empty', + 'modified': 'Modified', + 'unchanged': 'Unchanged', + }, + }, + }, + { + label: 'Data Scope', + show: true, + filter: { + name: 'datascope', + type: 'select', + options: { + 'candidate': 'Candidate', + 'session': 'Session', + 'project': 'Project', + }, + }, + }, + { + label: 'Data Type', + show: true, + filter: { + name: 'datatype', + type: 'text', + }, + }, + { + label: 'Data Cardinality', + show: true, + filter: { + name: 'cardinality', + type: 'select', + options: { + 'unique': 'Unique', + 'single': 'Single', + 'optional': 'Optional', + 'many': 'Many', + }, + }, + }, + { + // We may or may not have an 8th column depending + // on type, which we need for formatting other columns. + // We don't show or display a filter because it's only + // valid for some data types. + label: 'Field Options', + show: false, + filter: null, + }, + ]; + return ( + + ); + } +} + +DataDictIndex.propTypes = { + dataURL: PropTypes.string.isRequired, +}; + +window.addEventListener('load', () => { + ReactDOM.render( + , + document.getElementById('lorisworkspace') + ); +}); diff --git a/modules/dictionary/php/categories.class.inc b/modules/dictionary/php/categories.class.inc new file mode 100644 index 00000000000..dc6dbd902ed --- /dev/null +++ b/modules/dictionary/php/categories.class.inc @@ -0,0 +1,73 @@ +getQueryParams(); + $user = $request->getAttribute("user"); + if (isset($queryparams['module'])) { + return $this->moduleDictionary($request, $user, $queryparams['module']); + } + + $modulesandcats = $this->Module->getUserModuleCategories( + $user, + $request->getAttribute("loris"), + ); + + $modulesassoc = []; + + foreach($modulesandcats['Modules'] as $module) { + $modulesassoc[$module->getName()] = $module->getLongName(); + } + + return new \LORIS\Http\Response\JSON\OK( [ + 'modules' => $modulesassoc, + 'categories' => $modulesandcats['Categories'], + ]); + } + + public function moduleDictionary(ServerRequestInterface $request, \User $user, string $modulename) : ResponseInterface + { + + $loris = $this->lorisinstance; + foreach ($loris->getActiveModules() as $m) { + if ($m->getName() == $modulename) { + $module = $m; + break; + } + } + + if($module === null) { + return new \LORIS\Http\Response\JSON\NotFound('module not found'); + } + + $data = $this->Module->getUserModuleCategories($user, $loris, $modulename); + $prov = (new DataDictRowProvisioner($loris, $data['CategoryItems'])) + ->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); + + + $organized = []; + foreach ($prov->execute($user) as $row) { + $cat = $row->getCategory(); + if (!isset($organized[$cat])) { + $organized[$cat] = []; + } + $fieldname = $row->getFieldName(); + $datatype = $row->getDataType(); + $organized[$cat][$fieldname] = [ + 'description' => $row->getDescription(), + 'scope' => $row->getScope(), + 'type' => $row->getDataType(), + 'cardinality' => $row->getCardinality(), + ]; + if ($datatype instanceof \LORIS\Data\Types\Enumeration) { + $organized[$cat][$fieldname]['options'] = $datatype->getOptions(); + } + } + return new \LORIS\Http\Response\JSON\OK($organized); + } +} diff --git a/modules/dictionary/php/datadictrow.class.inc b/modules/dictionary/php/datadictrow.class.inc new file mode 100644 index 00000000000..1a53b588972 --- /dev/null +++ b/modules/dictionary/php/datadictrow.class.inc @@ -0,0 +1,78 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ + +namespace LORIS\dictionary; + +/** + * A DataDictRow represents a row in the datadict menu table. + * + * @category Behavioural + * @package Main + * @subpackage Behavioural + * @author Dave MacFarlane + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\AccessibleResource +{ + protected $DBRow; + + /** + * Create a new DataDictRow + * + * @param array $row The row (in the same format as \Database::pselectRow + * returns + */ + public function __construct(\LORIS\Data\Dictionary\DictionaryItem $item, array $row) + { + $this->item = $item; + $this->DBRow = $row; + } + + /** + * Implements \LORIS\Data\DataInstance interface for this row. + * + * @return array which can be serialized by json_encode() + */ + public function jsonSerialize() : array + { + return $this->DBRow; + } + + public function isAccessibleBy(\User $user) : bool { + return $this->item->isAccessibleBy($user); + } + + public function getCategory() : string { + return $this->DBRow['category']; + } + + public function getFieldName() : string { + return $this->DBRow['field']; + } + public function getDescription() : string { + return $this->DBRow['description']; + } + public function getScope() : \LORIS\Data\Scope { + return $this->DBRow['datascope']; + } + public function getDataType() : \LORIS\Data\Type{ + return $this->DBRow['type']; + } + public function getCardinality() : \LORIS\Data\Cardinality { + return $this->DBRow['cardinality']; + } + +} diff --git a/modules/dictionary/php/datadictrowprovisioner.class.inc b/modules/dictionary/php/datadictrowprovisioner.class.inc new file mode 100644 index 00000000000..4cfb941595d --- /dev/null +++ b/modules/dictionary/php/datadictrowprovisioner.class.inc @@ -0,0 +1,115 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ + +namespace LORIS\dictionary; + +/** + * This class implements a data provisioner to get all possible rows + * for the datadict menu page. + * + * PHP Version 7 + * + * @category Behavioural + * @package Main + * @subpackage Behavioural + * @author Dave MacFarlane + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance +{ + /** + * Create a DataDictRowProvisioner, which gets rows for + * the datadict menu table. + * + * @param \LORIS\LorisInstance $loris The Loris instance whose + * dictionary should be retrieved + */ + function __construct(\LORIS\LorisInstance $loris, array $items) + { + $this->loris = $loris; + $this->items = $items; + } + + /** + * Get all dictionary instances, honouring parameter_type_override + * if applicable + * + * @return \Traversable + */ + public function getAllInstances() : \Traversable + { + $modules = $this->loris->getActiveModules(); + $DB = $this->loris->getDatabaseConnection(); + + $dict = []; + + $overrides = $DB->pselectWithIndexKey( + "SELECT Name, Description FROM parameter_type_override", + [], + "Name" + ); + + foreach ($this->items as $row) { + $module = $row['Module']; + $cat = $row['Category']; + foreach ($cat->getItems() as $item) { + $name = $item->getName(); + $desc = ''; + $status = 'Unchanged'; + if (isset($overrides[$name])) { + $desc = $overrides[$name]['Description']; + $status = 'Modified'; + } else { + $desc = $item->getDescription(); + } + + if ($desc == '') { + $status = 'Empty'; + } + $dict[] = $this->getInstance( + $item, + [ + 'module' => $module->getName(), + 'category' => $cat->getName(), + 'field' => $item->getName(), + 'description' => $desc, + 'description_status' => $status, + 'datascope' => $item->getScope(), + 'type' => $item->getDataType(), + 'cardinality' => $item->getCardinality(), + ] + ); + } + } + return new \ArrayIterator($dict); + } + /** + * Returns an instance of a DataDict object for a given + * table row. + * + * @param array $row The database row from the LORIS Database class. + * + * @return \LORIS\Data\DataInstance An instance representing this row. + */ + public function getInstance(\LORIS\Data\Dictionary\DictionaryItem $item, array $row) : \LORIS\Data\DataInstance + { + $itype = $item->getDataType(); + if ($itype instanceof \LORIS\Data\Types\Enumeration) { + $row['options'] = $itype->getOptions(); + } + return new DataDictRow($item, $row); + } +} diff --git a/modules/dictionary/php/dictionary.class.inc b/modules/dictionary/php/dictionary.class.inc new file mode 100644 index 00000000000..a1376c94d0f --- /dev/null +++ b/modules/dictionary/php/dictionary.class.inc @@ -0,0 +1,143 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://github.com/aces/Loris-Trunk + */ + +namespace LORIS\dictionary; +use \Psr\Http\Message\ServerRequestInterface; +/** + * Datadict module + * + * PHP version 7 + * + * @category Datadict + * @package Main + * @author Ted Strauss + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://github.com/aces/Loris-Trunk + */ + +class Dictionary extends \DataFrameworkMenu +{ + /** + * Overloading this method to allow access to site users (their own site + * only) and users w/ multisite privs + * + * @param \User $user The user whose access is being checked + * + * @return bool + */ + function _hasAccess(\User $user) : bool + { + return ($user->hasPermission('data_dict_view') || + $user->hasPermission('data_dict_edit')); + } + + /** + * Tells the base class that this page's provisioner can support the + * HasAnyPermissionOrUserSiteMatch filter. + * + * @return ?array of site permissions or null + */ + public function allSitePermissionNames() : ?array + { + return null; + } + + /** + * Returns a list of instruments to use as the "Source From" + * filter options + * + * @return array Dynamic field options + */ + public function getFieldOptions() : array + { + $amodules = []; + foreach($this->modules as $module) { + $amodules[$module->getName()] = $module->getLongName(); + } + return [ + 'modules' => $amodules, + 'categories' => $this->categories, + ]; + } + + /** + * Gets the data source for this menu filter. + * + * @return \LORIS\Data\Provisioner + */ + public function getBaseDataProvisioner() : \LORIS\Data\Provisioner + { + return new DataDictRowProvisioner($this->lorisinstance, $this->categoryitems); + } + + /** + * Include the column formatter required to make the content editable in + * the datadict menu + * + * @return array of javascript to be inserted + **/ + function getJSDependencies() + { + $factory = \NDB_Factory::singleton(); + $baseURL = $factory->settings()->getBaseURL(); + $deps = parent::getJSDependencies(); + return array_merge( + $deps, + [ + $baseURL . "/datadict/js/dataDictIndex.js", + ] + ); + } + + /** + * Generate a breadcrumb trail for this page. + * + * @return \LORIS\BreadcrumbTrail + */ + public function getBreadcrumbs(): \LORIS\BreadcrumbTrail + { + return new \LORIS\BreadcrumbTrail( + new \LORIS\Breadcrumb('Data Dictionary', "/$this->name") + ); + } + + /** + * The datadict module does not have any concept of a project. + * + * @return bool + */ + public function useProjectFilter() : bool + { + return false; + } + + // FIXME: This should be moved to the parent class + public function getDataProvisionerWithFilters() : \LORIS\Data\Provisioner { + $prov = parent::getDataProvisionerWithFilters(); + return $prov->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); + } + + public function loadResources( + \User $user, ServerRequestInterface $request + ) : void { + $this->lorisinstance = $request->getAttribute("loris"); + $modulesandcats = $this->Module->getUserModuleCategories( + $user, + $this->lorisinstance, + ); + + $this->categoryitems = $modulesandcats['CategoryItems']; + $this->modules = $modulesandcats['Modules']; + $this->categories = $modulesandcats['Categories']; + } +} diff --git a/modules/dictionary/php/fields.class.inc b/modules/dictionary/php/fields.class.inc new file mode 100644 index 00000000000..f07a391ae23 --- /dev/null +++ b/modules/dictionary/php/fields.class.inc @@ -0,0 +1,128 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://github.com/aces/Loris-Trunk + */ +namespace LORIS\dictionary; + +use \Psr\Http\Message\ServerRequestInterface; +use \Psr\Http\Message\ResponseInterface; +/** + * Datadict module + * + * PHP version 7 + * + * @category Datadict + * @package Main + * @author Ted Strauss + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://github.com/aces/Loris-Trunk + */ + +class Fields extends \NDB_Page +{ + /** + * {@inheritDoc} + * + * @param \User $user The user whose access is being checked + * + * @return bool + */ + function _hasAccess(\User $user) : bool + { + return $user->hasPermission('data_dict_edit'); + } + + /** + * The modules class overrides the default behaviour to handle PATCH + * requests for module_manager/modules/$modulename. + * + * @param ServerRequestInterface $request The incoming PSR7 request + * + * @return ResponseInterface + */ + public function handle(ServerRequestInterface $request) : ResponseInterface + { + $user = $request->getAttribute("user"); + if ($user === null && !($user instanceof \User)) { + return new \LORIS\Http\Response\JSON\InternalServerError( + "No valid user" + ); + } + if (!($this->_hasAccess($user))) { + return new \LORIS\Http\Response\JSON\Forbidden(); + } + + $path = $request->getURI()->getPath(); + if (strpos($path, "fields/") === false) { + // if the path doesn't contain "fields" we shouldn't have + // delegated to this class.. + throw new \LorisException("Invalid internal LORIS state"); + } + $name = substr($path, strpos($path,"fields/") + strlen("fields/")); + $origdesc = $this->getOriginalDescription($name); + switch ($request->getMethod()) { + case 'PUT': + $status = $this->replaceDescription($name, $request->getBody()->__toString()); + return (new \LORIS\Http\Response()) + ->withHeader("Content-Type", "text/plain") + ->withHeader("X-StatusDesc", $status) + ->withStatus(201) + ->withBody( + new \LORIS\Http\StringStream("") + ); + default: + return new \LORIS\Http\Response\JSON\MethodNotAllowed( + ['PUT'] + ); + } + } + + private function getOriginalDescription(string $name) : string { + $modules = $this->lorisinstance->getActiveModules(); + + // Brute force finding the name since we don't know what module + // the dictionary item came from. + foreach ($modules as $module) { + $mdict = $module->getDataDictionary($this->lorisinstance); + foreach($mdict as $cat) { + foreach($cat->getItems() as $item) { + $iname = $cat->getName() . '_' . $item->getName(); + if ($name === $iname) { + return $item->getDescription(); + } + } + } + } + return ''; + } + + private function replaceDescription(string $name, string $description) : string { + $DB = $this->lorisinstance->getDatabaseConnection(); + $origdesc = $this->getOriginalDescription($name); + + if ($description == $origdesc) { + $DB->delete( + 'parameter_type_override', + ['Name' => $name] + ); + return "Unchanged"; + } else { + $DB->replace( + 'parameter_type_override', + [ + 'Description' => $description, + 'Name' => $name, + ] + ); + return "Modified"; + } + } +} diff --git a/modules/dictionary/php/module.class.inc b/modules/dictionary/php/module.class.inc new file mode 100644 index 00000000000..eba42b1d0a3 --- /dev/null +++ b/modules/dictionary/php/module.class.inc @@ -0,0 +1,109 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ +namespace LORIS\dictionary; + +/** + * Class module implements the basic LORIS module functionality + * + * @category Behavioural + * @package Main + * @author Dave MacFarlane + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ +class Module extends \Module +{ + /** + * {@inheritDoc} + * + * @param \User $user The user whose access is being checked. + * + * @return bool whether access is granted + */ + public function hasAccess(\User $user) : bool + { + return parent::hasAccess($user) && + $user->hasAnyPermission(['data_dict_view', 'data_dict_edit']); + } + + /** + * {@inheritDoc} + * + * @return string The menu category for this module + */ + public function getMenuCategory() : string + { + return "Tools"; + } + + /** + * {@inheritDoc} + * + * @return string The human readable name for this module + */ + public function getLongName() : string + { + return "Data Dictionary (Beta)"; + } + + /** + * Return a list of modules and categories that this user + * has access to. + * + * This should not be used outside of this module, it's only + * to store shared code between different endpoints + * + * @return array + */ + public function getUserModuleCategories(\User $user, \LORIS\LorisInstance $loris, ?string $formodule=null) : array + { + $modules = $loris->getActiveModules(); + $usermodules = []; + $dict = []; + $categories = []; + + $categoryitems = []; + foreach ($modules as $module) { + if ($formodule !== null && $module->getName() !== $formodule) { + continue; + } + if(!$module->hasAccess($user)) { + continue; + } + + $mdict = $module->getDataDictionary($this->lorisinstance); + $mname = $module->getName(); + + if(count($mdict) > 0) { + $usermodules[] = $module; + $categories[$mname] = []; + + + foreach($mdict as $cat) { + $categories[$mname][$cat->getName()] = $cat->getDescription(); + $categoryitems[] = [ + 'Module' => $module, + 'Category' => $cat, + ]; + } + } + } + return [ + 'Modules' => $usermodules, + 'Categories' => $categories, + 'CategoryItems' => $categoryitems, + ]; + } +} diff --git a/modules/dictionary/test/TestPlan.md b/modules/dictionary/test/TestPlan.md new file mode 100644 index 00000000000..12dd19c9844 --- /dev/null +++ b/modules/dictionary/test/TestPlan.md @@ -0,0 +1,39 @@ +# Data Dictionary - Test Plan + +1. Check that you have access to the Data Dictionary if the user has one or more of +these permissions: + - data_dict + - data_dict_edit or superuser. + [Automation Testing] +2. Check that the instruments combo box contains all the instrument names. + [Automation Testing] +3. Perform various searches and validate the results. + - Use any combination of the available search criteria. + - Make sure that when performing a keyword search the algorithm takes column Name, + SourceField and Description into account. + - Make sure that when you choose "Empty" in the Description combo box, all the rows + returned have an empty description. + [Automation Testing] +4. Check that selecting multiple instruments returns all relevant results. +5. Check that the table can be sorted according to any column (ascending and descending). + [Automation Testing] +6. Check that pushing the Clear button sets the Description search field to 'All', +the Instruments search field to 'All instruments', clears the search keyword text +field and performs a search with these criteria. Validate the results. + [Automation Testing] +7. Check that if (and only if) you have the 'data_dict_edit' permission you can edit +the Description field. Edit a description, access the Candidate Profile page and +access the Data Dictionary page again. Make sure the edit was saved. + [Automation Testing] +8. Make sure that search keywords are not case-sensitive and that when you specify +more than one keyword, the search returns entries that match the whole string. + [Automation Testing] +9. Check that you can navigate through the search result pages (both by clicking on +the numbers and arrows in the bottom right corner of the table). + [Automation Testing] +10. Check that the maximum rows displayed dropdown works, also check that the +download table as CSV works as well. + [Automation Testing] +11. Check that the `tools/exporters/data_dictionary_builder.php` works. Try changing +field names in an instrument before running the script and make sure that the +corresponding entries in Data Dictionary are updated correctly. diff --git a/modules/dictionary/test/datadictTest.php b/modules/dictionary/test/datadictTest.php new file mode 100644 index 00000000000..bc710b752c5 --- /dev/null +++ b/modules/dictionary/test/datadictTest.php @@ -0,0 +1,117 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://github.com/aces/Loris + */ +use Facebook\WebDriver\WebDriverBy; +use Facebook\WebDriver\WebDriverExpectedCondition; +require_once __DIR__ . + "/../../../test/integrationtests/LorisIntegrationTest.class.inc"; +/** + * Datadict automated integration tests + * + * PHP Version 5 + * + * @category Test + * @package Loris + * @author Ted Strauss + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://github.com/aces/Loris + */ +class DatadictTestIntegrationTest extends LorisIntegrationTest +{ + /** + * UI elements and locations + * breadcrumb - 'Access Profile' + * Table headers + */ + private $_loadingUI + = [ + 'Data Dictionary' => '#bc2 > a:nth-child(2) > div', + 'Source From' => '#dynamictable > thead > tr > th:nth-child(2)', + 'Name' => '#dynamictable > thead > tr > th:nth-child(3)', + 'Source Field' => '#dynamictable > thead > tr > th:nth-child(4)', + 'Description' => '#dynamictable > thead > tr > th:nth-child(5)', + 'Description Status' => '#dynamictable > thead > tr > th:nth-child(6)', + ]; + + /** + * Inserting testing data + * + * @return void + */ + function setUp() + { + parent::setUp(); + $this->DB->insert( + "parameter_type", + [ + 'Name' => 'TestParameterNotRealMAGICNUMBER335', + 'Type' => 'varchar(255)', + 'Description' => 'I am a fake description used only for testing'. + ' you should not see me. MAGICNUMBER335', + 'SourceFrom' => 'nowhere', + 'SourceField' => 'imaginary', + 'Queryable' => true, + 'IsFile' => 0, + ] + ); + } + /** + * Deleting testing data + * + * @return void + */ + function tearDown() + { + parent::tearDown(); + $this->DB->delete( + 'parameter_type', + ['Name' => 'TestParameterNotRealMAGICNUMBER335'] + ); + } + /** + * Tests that, when loading the datadict module, some + * text appears in the body. + * + * @return void + */ + function testDatadictDoespageLoad() + { + $this->webDriver->get($this->url . "/datadict/"); + + $this->webDriver->wait(120, 1000)->until( + WebDriverExpectedCondition::presenceOfElementLocated( + WebDriverBy::Name("Name") + ) + ); + + $bodyText = $this->webDriver->findElement( + WebDriverBy::cssSelector("body") + )->getText(); + $this->assertContains("Data Dictionary", $bodyText); + } + /** + * Testing UI elements when page loads + * + * @return void + */ + function testPageUIs() + { + $this->safeGet($this->url . "/datadict/"); + foreach ($this->_loadingUI as $key => $value) { + $text = $this->safeFindElement( + WebDriverBy::cssSelector($value) + )->getText(); + $this->assertContains($key, $text); + } + } +} + From e97248c0b3c16105dde880622f5398ba5a72dfe4 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 24 Aug 2020 11:20:49 -0400 Subject: [PATCH 22/60] Remove unused file --- modules/dictionary/ajax/UpdateDataDict.php | 65 ---------------------- 1 file changed, 65 deletions(-) delete mode 100644 modules/dictionary/ajax/UpdateDataDict.php diff --git a/modules/dictionary/ajax/UpdateDataDict.php b/modules/dictionary/ajax/UpdateDataDict.php deleted file mode 100644 index 063df666c69..00000000000 --- a/modules/dictionary/ajax/UpdateDataDict.php +++ /dev/null @@ -1,65 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris-Trunk/ - */ - -$user =& User::singleton(); -if (!$user->hasPermission('data_dict_edit')) { - header("HTTP/1.1 403 Forbidden"); - exit; -} - -set_include_path(get_include_path().":../project/libraries:../php/libraries:"); -ini_set('default_charset', 'utf-8'); -/* This is used by the data dictionary page to - * update the column descriptions on the fly */ -require_once "Database.class.inc"; -require_once 'NDB_Config.class.inc'; -require_once 'NDB_Client.class.inc'; -$config =& NDB_Config::singleton(); -$client = new NDB_Client(); -$client->initialize(); - - -list($name,$extra) = explode("___", $_REQUEST['fieldname']); - -$description = trim($_REQUEST['description']); - -// create user object -$user =& User::singleton(); - -if ($user->hasPermission('data_dict_edit')) { //if user has edit permission - - $native_description = $DB->pselectOne( - "SELECT Description FROM parameter_type WHERE Name = :v_name", - ["v_name" => $name] - ); - - if ($description != $native_description) { - if (empty($description)) { - $description = ' '; - } - $DB->replace( - 'parameter_type_override', - [ - 'Description' => $description, - 'Name' => $name, - ] - ); - } else { - $DB->delete( - 'parameter_type_override', - ['Name' => $name] - ); - } -} - - From df900bd2ae0a6c02b3271ef99be91423297fb3cd Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 24 Aug 2020 11:53:21 -0400 Subject: [PATCH 23/60] Move serialization to datainstance, not provisioner --- modules/dictionary/js/datadict_helper.js | 25 -------- modules/dictionary/jsx/dataDictIndex.js | 6 +- modules/dictionary/php/datadictrow.class.inc | 50 ++++++++++++---- .../php/datadictrowprovisioner.class.inc | 60 +++++++++---------- modules/dictionary/php/dictionary.class.inc | 16 ++--- webpack.config.js | 1 + 6 files changed, 75 insertions(+), 83 deletions(-) delete mode 100644 modules/dictionary/js/datadict_helper.js diff --git a/modules/dictionary/js/datadict_helper.js b/modules/dictionary/js/datadict_helper.js deleted file mode 100644 index 192eaf88db9..00000000000 --- a/modules/dictionary/js/datadict_helper.js +++ /dev/null @@ -1,25 +0,0 @@ -function save() { - - $('.description'). - bind('blur',function(event){ - event.stopImmediatePropagation(); - id = event.target.id; - value = $("#" + id) .text(); - // sendRemoteDataQuery("query_gui_data_loader.php?mode=loadQuery&action="+action+"&qid="+qid); - $.get(loris.BaseURL + "/datadict/ajax/UpdateDataDict.php?fieldname=" + id + "&description=" + value, function(data) { - } - ); - } - ).keypress(function(e) { - if(e.which === 13) { // Determine if the user pressed the enter button - $(this).blur(); - } - - }); -}; - - -$(function(){ - save(); -}); - diff --git a/modules/dictionary/jsx/dataDictIndex.js b/modules/dictionary/jsx/dataDictIndex.js index d5d11da0acb..419c3dd1066 100644 --- a/modules/dictionary/jsx/dataDictIndex.js +++ b/modules/dictionary/jsx/dataDictIndex.js @@ -86,7 +86,7 @@ class DataDictIndex extends Component { } const url = this.props.BaseURL - + '/datadict/fields/' + + '/dictionary/fields/' + encodeURI(row['Field Name']); // The fetch happens asyncronously, which means that the @@ -320,7 +320,7 @@ class DataDictIndex extends Component { ]; return ( { ReactDOM.render( , document.getElementById('lorisworkspace') diff --git a/modules/dictionary/php/datadictrow.class.inc b/modules/dictionary/php/datadictrow.class.inc index 1a53b588972..85a1caa918f 100644 --- a/modules/dictionary/php/datadictrow.class.inc +++ b/modules/dictionary/php/datadictrow.class.inc @@ -27,18 +27,24 @@ namespace LORIS\dictionary; */ class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\AccessibleResource { - protected $DBRow; - /** * Create a new DataDictRow * * @param array $row The row (in the same format as \Database::pselectRow * returns */ - public function __construct(\LORIS\Data\Dictionary\DictionaryItem $item, array $row) - { + public function __construct( + \Module $itemmodule, + \LORIS\Data\Dictionary\Category $cat, + \LORIS\Data\Dictionary\DictionaryItem $item, + string $desc, + string $descstatus + ) { + $this->itemmodule = $itemmodule; + $this->itemcategory = $cat; $this->item = $item; - $this->DBRow = $row; + $this->desc = $desc; + $this->status = $descstatus; } /** @@ -48,7 +54,24 @@ class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\Acce */ public function jsonSerialize() : array { - return $this->DBRow; + $val = [ + 'module' => $this->itemmodule->getName(), + 'category' => $this->itemcategory->getName(), + 'field' => $this->getFieldName(), + 'description' => $this->desc, + 'description_status' => $this->status, + 'datascope' => $this->getScope(), + 'type' => $this->getScope(), + 'cardinality' => $this->getCardinality(), + ]; + + $itype = $this->item->getDataType(); + if ($itype instanceof \LORIS\Data\Types\Enumeration) { + $val['options'] = $itype->getOptions(); + } + + + return $val; } public function isAccessibleBy(\User $user) : bool { @@ -56,23 +79,26 @@ class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\Acce } public function getCategory() : string { - return $this->DBRow['category']; + return $this->item->getName(); } public function getFieldName() : string { - return $this->DBRow['field']; + return $this->item->getName(); } + public function getDescription() : string { return $this->DBRow['description']; } public function getScope() : \LORIS\Data\Scope { - return $this->DBRow['datascope']; + return $this->item->getScope(); } - public function getDataType() : \LORIS\Data\Type{ - return $this->DBRow['type']; + + public function getDataType() : \LORIS\Data\Type { + return $this->item->getDataType(); + } public function getCardinality() : \LORIS\Data\Cardinality { - return $this->DBRow['cardinality']; + return $this->item->getCardinality(); } } diff --git a/modules/dictionary/php/datadictrowprovisioner.class.inc b/modules/dictionary/php/datadictrowprovisioner.class.inc index 4cfb941595d..dd2a1eb5a14 100644 --- a/modules/dictionary/php/datadictrowprovisioner.class.inc +++ b/modules/dictionary/php/datadictrowprovisioner.class.inc @@ -65,33 +65,30 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance foreach ($this->items as $row) { $module = $row['Module']; $cat = $row['Category']; + + error_log("Hello!!!"); foreach ($cat->getItems() as $item) { - $name = $item->getName(); - $desc = ''; - $status = 'Unchanged'; - if (isset($overrides[$name])) { - $desc = $overrides[$name]['Description']; - $status = 'Modified'; - } else { - $desc = $item->getDescription(); - } + $name = $item->getName(); + $desc = ''; + $status = 'Unchanged'; + if (isset($overrides[$name])) { + $desc = $overrides[$name]['Description']; + $status = 'Modified'; + } else { + $desc = $item->getDescription(); + } + + if ($desc == '') { + $status = 'Empty'; + } - if ($desc == '') { - $status = 'Empty'; - } - $dict[] = $this->getInstance( - $item, - [ - 'module' => $module->getName(), - 'category' => $cat->getName(), - 'field' => $item->getName(), - 'description' => $desc, - 'description_status' => $status, - 'datascope' => $item->getScope(), - 'type' => $item->getDataType(), - 'cardinality' => $item->getCardinality(), - ] - ); + $dict[] = $this->getInstance( + $module, + $cat, + $item, + $desc, + $status, + ); } } return new \ArrayIterator($dict); @@ -104,12 +101,13 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance * * @return \LORIS\Data\DataInstance An instance representing this row. */ - public function getInstance(\LORIS\Data\Dictionary\DictionaryItem $item, array $row) : \LORIS\Data\DataInstance + public function getInstance( + \Module $module, + \LORIS\Data\Dictionary\Category $cat, + \LORIS\Data\Dictionary\DictionaryItem $item, + string $desc, + string $descstatus) : \LORIS\Data\DataInstance { - $itype = $item->getDataType(); - if ($itype instanceof \LORIS\Data\Types\Enumeration) { - $row['options'] = $itype->getOptions(); - } - return new DataDictRow($item, $row); + return new DataDictRow($module, $cat, $item, $desc, $descstatus); } } diff --git a/modules/dictionary/php/dictionary.class.inc b/modules/dictionary/php/dictionary.class.inc index a1376c94d0f..fd867e849ce 100644 --- a/modules/dictionary/php/dictionary.class.inc +++ b/modules/dictionary/php/dictionary.class.inc @@ -1,18 +1,10 @@ - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://github.com/aces/Loris-Trunk - */ +declare(strict_types=1); namespace LORIS\dictionary; + use \Psr\Http\Message\ServerRequestInterface; + /** * Datadict module * @@ -94,7 +86,7 @@ class Dictionary extends \DataFrameworkMenu return array_merge( $deps, [ - $baseURL . "/datadict/js/dataDictIndex.js", + $baseURL . "/dictionary/js/dataDictIndex.js", ] ); } diff --git a/webpack.config.js b/webpack.config.js index 03466b0b14e..5ca35192c57 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -289,6 +289,7 @@ const config = [ lorisModule('instruments', ['CandidateInstrumentList']), lorisModule('candidate_profile', ['CandidateInfo']), lorisModule('datadict', ['dataDictIndex']), + lorisModule('dictionary', ['dataDictIndex']), ]; // Support project overrides From 5d2926ff19e112429ea591dd68b241923c1df7d4 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 24 Aug 2020 13:57:05 -0400 Subject: [PATCH 24/60] Some PHPCS --- modules/dictionary/jsx/dataDictIndex.js | 4 - modules/dictionary/php/categories.class.inc | 68 ++++++--- modules/dictionary/php/datadictrow.class.inc | 131 +++++++++++------- .../php/datadictrowprovisioner.class.inc | 95 ++++++------- modules/dictionary/php/dictionary.class.inc | 36 +++-- modules/dictionary/php/fields.class.inc | 74 +++++----- modules/dictionary/php/module.class.inc | 42 ++++-- modules/imaging_browser/php/module.class.inc | 20 ++- 8 files changed, 283 insertions(+), 187 deletions(-) diff --git a/modules/dictionary/jsx/dataDictIndex.js b/modules/dictionary/jsx/dataDictIndex.js index 419c3dd1066..f69efbefa23 100644 --- a/modules/dictionary/jsx/dataDictIndex.js +++ b/modules/dictionary/jsx/dataDictIndex.js @@ -12,10 +12,6 @@ import swal from 'sweetalert2'; * * Renders Data Dictionary main page consisting of FilterTable and * DataTable components. - * - * @author Liza Levitis - * @version 1.0.0 - * */ class DataDictIndex extends Component { /** diff --git a/modules/dictionary/php/categories.class.inc b/modules/dictionary/php/categories.class.inc index dc6dbd902ed..93a20704d37 100644 --- a/modules/dictionary/php/categories.class.inc +++ b/modules/dictionary/php/categories.class.inc @@ -3,16 +3,33 @@ namespace LORIS\dictionary; use \Psr\Http\Message\ServerRequestInterface; use \Psr\Http\Message\ResponseInterface; -class Categories extends \NDB_Page { - +/** + * Categories page returns a list of valid module and their categories + * which both have a valid data dictionary and the user accessing the + * page has access to. + * + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + */ +class Categories extends \NDB_Page +{ + /** + * Override the hndle to return a list of valid modules and categories + * that the user has access to. + * + * @param ServerRequestInterface $request the incoming PSR7 request + * + * @return ResponseInterface + */ public function handle(ServerRequestInterface $request) : ResponseInterface { $queryparams = $request->getQueryParams(); - $user = $request->getAttribute("user"); + $user = $request->getAttribute("user"); if (isset($queryparams['module'])) { return $this->moduleDictionary($request, $user, $queryparams['module']); } + // getUserModuleCategories enforces permissions and strips out modules + // that don't have any dictionary. $modulesandcats = $this->Module->getUserModuleCategories( $user, $request->getAttribute("loris"), @@ -20,19 +37,33 @@ class Categories extends \NDB_Page { $modulesassoc = []; - foreach($modulesandcats['Modules'] as $module) { + foreach ($modulesandcats['Modules'] as $module) { $modulesassoc[$module->getName()] = $module->getLongName(); } - return new \LORIS\Http\Response\JSON\OK( [ - 'modules' => $modulesassoc, - 'categories' => $modulesandcats['Categories'], - ]); + return new \LORIS\Http\Response\JSON\OK( + [ + 'modules' => $modulesassoc, + 'categories' => $modulesandcats['Categories'], + ] + ); } - public function moduleDictionary(ServerRequestInterface $request, \User $user, string $modulename) : ResponseInterface - { - + /** + * TODO: Move this + * + * @param ServerRequestInterface $request The request + * @param \User $user The user accessing the dictionary + * @param string $modulename The module whose dictionary should + * be retrieved + * + * @return ResponseInterface + */ + public function moduleDictionary( + ServerRequestInterface $request, + \User $user, + string $modulename + ) : ResponseInterface { $loris = $this->lorisinstance; foreach ($loris->getActiveModules() as $m) { if ($m->getName() == $modulename) { @@ -41,7 +72,7 @@ class Categories extends \NDB_Page { } } - if($module === null) { + if ($module === null) { return new \LORIS\Http\Response\JSON\NotFound('module not found'); } @@ -49,7 +80,6 @@ class Categories extends \NDB_Page { $prov = (new DataDictRowProvisioner($loris, $data['CategoryItems'])) ->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); - $organized = []; foreach ($prov->execute($user) as $row) { $cat = $row->getCategory(); @@ -57,13 +87,13 @@ class Categories extends \NDB_Page { $organized[$cat] = []; } $fieldname = $row->getFieldName(); - $datatype = $row->getDataType(); + $datatype = $row->getDataType(); $organized[$cat][$fieldname] = [ - 'description' => $row->getDescription(), - 'scope' => $row->getScope(), - 'type' => $row->getDataType(), - 'cardinality' => $row->getCardinality(), - ]; + 'description' => $row->getDescription(), + 'scope' => $row->getScope(), + 'type' => $row->getDataType(), + 'cardinality' => $row->getCardinality(), + ]; if ($datatype instanceof \LORIS\Data\Types\Enumeration) { $organized[$cat][$fieldname]['options'] = $datatype->getOptions(); } diff --git a/modules/dictionary/php/datadictrow.class.inc b/modules/dictionary/php/datadictrow.class.inc index 85a1caa918f..6571d822527 100644 --- a/modules/dictionary/php/datadictrow.class.inc +++ b/modules/dictionary/php/datadictrow.class.inc @@ -1,37 +1,27 @@ - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ - namespace LORIS\dictionary; +use \LORIS\Data\Dictionary\Category; +use \LORIS\Data\Dictionary\DictionaryItem; /** - * A DataDictRow represents a row in the datadict menu table. + * A DataDictRow represents a row in the datadict menu table, which + * is a DictionaryItem with the category and overridden Description + * added. * - * @category Behavioural - * @package Main - * @subpackage Behavioural - * @author Dave MacFarlane - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 */ -class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\AccessibleResource +class DataDictRow implements \LORIS\Data\DataInstance, + \LORIS\StudyEntities\AccessibleResource { /** - * Create a new DataDictRow + * Construct a DataDictRow * - * @param array $row The row (in the same format as \Database::pselectRow - * returns + * @param \Module $itemmodule The module that the item is from + * @param Category $cat The way the module is categorized in the + * item + * @param DictionaryItem $item The DictionaryItem for this row + * @param string $desc The (possibly overridden) description + * @param string $descstatus The status of the description override */ public function __construct( \Module $itemmodule, @@ -40,11 +30,11 @@ class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\Acce string $desc, string $descstatus ) { - $this->itemmodule = $itemmodule; + $this->itemmodule = $itemmodule; $this->itemcategory = $cat; - $this->item = $item; - $this->desc = $desc; - $this->status = $descstatus; + $this->item = $item; + $this->desc = $desc; + $this->status = $descstatus; } /** @@ -55,14 +45,14 @@ class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\Acce public function jsonSerialize() : array { $val = [ - 'module' => $this->itemmodule->getName(), - 'category' => $this->itemcategory->getName(), - 'field' => $this->getFieldName(), - 'description' => $this->desc, - 'description_status' => $this->status, - 'datascope' => $this->getScope(), - 'type' => $this->getScope(), - 'cardinality' => $this->getCardinality(), + 'module' => $this->itemmodule->getName(), + 'category' => $this->itemcategory->getName(), + 'field' => $this->getFieldName(), + 'description' => $this->desc, + 'description_status' => $this->status, + 'datascope' => $this->getScope(), + 'type' => $this->getScope(), + 'cardinality' => $this->getCardinality(), ]; $itype = $this->item->getDataType(); @@ -70,35 +60,80 @@ class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\Acce $val['options'] = $itype->getOptions(); } - return $val; } - public function isAccessibleBy(\User $user) : bool { + /** + * Implement the AccessibleResource interface by proxying to + * the underlying DictionaryItem. + * + * @param \User $user The user whose access should be checked + * + * @return bool + */ + public function isAccessibleBy(\User $user) : bool + { return $this->item->isAccessibleBy($user); } - public function getCategory() : string { - return $this->item->getName(); + /** + * Return the module category to which the DictionaryItem belongs + * + * @return string + */ + public function getCategory() : string + { + return $this->itemcategory; } - public function getFieldName() : string { + /** + * Return the field name for this row + * + * @return string + */ + public function getFieldName() : string + { return $this->item->getName(); } - public function getDescription() : string { - return $this->DBRow['description']; + /** + * Return the (possibly overridden) description for this row + * + * @return string + */ + public function getDescription() : string + { + return $this->desc; } - public function getScope() : \LORIS\Data\Scope { + + /** + * Return the data Scope for this row. + * + * @return \LORIS\Data\Scope + */ + public function getScope() : \LORIS\Data\Scope + { return $this->item->getScope(); } - public function getDataType() : \LORIS\Data\Type { + /** + * Return the data type for this DictionaryItem + * + * @return \LORIS\Data\Type + */ + public function getDataType() : \LORIS\Data\Type + { return $this->item->getDataType(); } - public function getCardinality() : \LORIS\Data\Cardinality { + + /** + * Return the data Cardinality for this item + * + * @return \LORIS\Data\Cardinality + */ + public function getCardinality() : \LORIS\Data\Cardinality + { return $this->item->getCardinality(); } - } diff --git a/modules/dictionary/php/datadictrowprovisioner.class.inc b/modules/dictionary/php/datadictrowprovisioner.class.inc index dd2a1eb5a14..d479a0a7497 100644 --- a/modules/dictionary/php/datadictrowprovisioner.class.inc +++ b/modules/dictionary/php/datadictrowprovisioner.class.inc @@ -1,32 +1,13 @@ - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ - namespace LORIS\dictionary; +use \LORIS\Data\Dictionary\DictionaryItem; +use \LORIS\Data\Dictionary\Category; /** * This class implements a data provisioner to get all possible rows * for the datadict menu page. * - * PHP Version 7 - * - * @category Behavioural - * @package Main - * @subpackage Behavioural - * @author Dave MacFarlane - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 */ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance { @@ -36,6 +17,9 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance * * @param \LORIS\LorisInstance $loris The Loris instance whose * dictionary should be retrieved + * @param array $items The list of dictionary items + * from all modules that this provisioner + * should iterate over. */ function __construct(\LORIS\LorisInstance $loris, array $items) { @@ -63,51 +47,58 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance ); foreach ($this->items as $row) { - $module = $row['Module']; - $cat = $row['Category']; + $module = $row['Module']; + $cat = $row['Category']; - error_log("Hello!!!"); - foreach ($cat->getItems() as $item) { - $name = $item->getName(); - $desc = ''; - $status = 'Unchanged'; - if (isset($overrides[$name])) { - $desc = $overrides[$name]['Description']; - $status = 'Modified'; - } else { - $desc = $item->getDescription(); - } + foreach ($cat->getItems() as $item) { + $name = $item->getName(); + $desc = ''; + $status = 'Unchanged'; + if (isset($overrides[$name])) { + $desc = $overrides[$name]['Description']; + $status = 'Modified'; + } else { + $desc = $item->getDescription(); + } - if ($desc == '') { - $status = 'Empty'; - } + if ($desc == '') { + $status = 'Empty'; + } - $dict[] = $this->getInstance( - $module, - $cat, - $item, - $desc, - $status, - ); - } + $dict[] = $this->getInstance( + $module, + $cat, + $item, + $desc, + $status, + ); + } } return new \ArrayIterator($dict); } + /** * Returns an instance of a DataDict object for a given - * table row. + * table row, adding the category and description override * - * @param array $row The database row from the LORIS Database class. + * @param \Module $module The module that this DictionaryItem came + * from. + * @param Category $cat The category within the module of the + * DictionaryItem. + * @param DictionaryItem $item The DictionaryItem for this row of the + * table + * @param string $desc The overridden description of the item + * @param string $descstatus The status of the description override * * @return \LORIS\Data\DataInstance An instance representing this row. */ public function getInstance( \Module $module, - \LORIS\Data\Dictionary\Category $cat, - \LORIS\Data\Dictionary\DictionaryItem $item, + Category $cat, + DictionaryItem $item, string $desc, - string $descstatus) : \LORIS\Data\DataInstance - { + string $descstatus + ) : \LORIS\Data\DataInstance { return new DataDictRow($module, $cat, $item, $desc, $descstatus); } } diff --git a/modules/dictionary/php/dictionary.class.inc b/modules/dictionary/php/dictionary.class.inc index fd867e849ce..9a94fa342d7 100644 --- a/modules/dictionary/php/dictionary.class.inc +++ b/modules/dictionary/php/dictionary.class.inc @@ -53,11 +53,11 @@ class Dictionary extends \DataFrameworkMenu public function getFieldOptions() : array { $amodules = []; - foreach($this->modules as $module) { + foreach ($this->modules as $module) { $amodules[$module->getName()] = $module->getLongName(); } return [ - 'modules' => $amodules, + 'modules' => $amodules, 'categories' => $this->categories, ]; } @@ -69,7 +69,10 @@ class Dictionary extends \DataFrameworkMenu */ public function getBaseDataProvisioner() : \LORIS\Data\Provisioner { - return new DataDictRowProvisioner($this->lorisinstance, $this->categoryitems); + return new DataDictRowProvisioner( + $this->lorisinstance, + $this->categoryitems + ); } /** @@ -113,23 +116,36 @@ class Dictionary extends \DataFrameworkMenu return false; } - // FIXME: This should be moved to the parent class - public function getDataProvisionerWithFilters() : \LORIS\Data\Provisioner { - $prov = parent::getDataProvisionerWithFilters(); - return $prov->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); + /** + * {@inheritDoc} + * + * @return \LORIS\Data\Provisioner + */ + public function getDataProvisionerWithFilters() : \LORIS\Data\Provisioner + { + return parent::getDataProvisionerWithFilters() + ->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); } + /** + * {@inheritDoc} + * + * @param \User $user The user this request is for + * @param ServerRequestInterface $request The PSR7 request + * + * @return void + */ public function loadResources( \User $user, ServerRequestInterface $request ) : void { $this->lorisinstance = $request->getAttribute("loris"); - $modulesandcats = $this->Module->getUserModuleCategories( + $modulesandcats = $this->Module->getUserModuleCategories( $user, $this->lorisinstance, ); $this->categoryitems = $modulesandcats['CategoryItems']; - $this->modules = $modulesandcats['Modules']; - $this->categories = $modulesandcats['Categories']; + $this->modules = $modulesandcats['Modules']; + $this->categories = $modulesandcats['Categories']; } } diff --git a/modules/dictionary/php/fields.class.inc b/modules/dictionary/php/fields.class.inc index f07a391ae23..a34d7e6c82c 100644 --- a/modules/dictionary/php/fields.class.inc +++ b/modules/dictionary/php/fields.class.inc @@ -1,15 +1,4 @@ - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://github.com/aces/Loris-Trunk - */ namespace LORIS\dictionary; use \Psr\Http\Message\ServerRequestInterface; @@ -17,13 +6,7 @@ use \Psr\Http\Message\ResponseInterface; /** * Datadict module * - * PHP version 7 - * - * @category Datadict - * @package Main - * @author Ted Strauss - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://github.com/aces/Loris-Trunk + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 */ class Fields extends \NDB_Page @@ -41,13 +24,13 @@ class Fields extends \NDB_Page } /** - * The modules class overrides the default behaviour to handle PATCH - * requests for module_manager/modules/$modulename. - * - * @param ServerRequestInterface $request The incoming PSR7 request - * - * @return ResponseInterface - */ + * The modules class overrides the default behaviour to handle PATCH + * requests for module_manager/modules/$modulename. + * + * @param ServerRequestInterface $request The incoming PSR7 request + * + * @return ResponseInterface + */ public function handle(ServerRequestInterface $request) : ResponseInterface { $user = $request->getAttribute("user"); @@ -66,11 +49,14 @@ class Fields extends \NDB_Page // delegated to this class.. throw new \LorisException("Invalid internal LORIS state"); } - $name = substr($path, strpos($path,"fields/") + strlen("fields/")); - $origdesc = $this->getOriginalDescription($name); + $name = substr($path, strpos($path, "fields/") + strlen("fields/")); + $origdesc = $this->_getOriginalDescription($name); switch ($request->getMethod()) { case 'PUT': - $status = $this->replaceDescription($name, $request->getBody()->__toString()); + $status = $this->_replaceDescription( + $name, + $request->getBody()->__toString() + ); return (new \LORIS\Http\Response()) ->withHeader("Content-Type", "text/plain") ->withHeader("X-StatusDesc", $status) @@ -85,16 +71,24 @@ class Fields extends \NDB_Page } } - private function getOriginalDescription(string $name) : string { + /** + * Get the original, non-overridden description for field named $name + * + * @param string $name The field name + * + * @return string + */ + private function _getOriginalDescription(string $name) : string + { $modules = $this->lorisinstance->getActiveModules(); // Brute force finding the name since we don't know what module // the dictionary item came from. foreach ($modules as $module) { $mdict = $module->getDataDictionary($this->lorisinstance); - foreach($mdict as $cat) { - foreach($cat->getItems() as $item) { - $iname = $cat->getName() . '_' . $item->getName(); + foreach ($mdict as $cat) { + foreach ($cat->getItems() as $item) { + $iname = $cat->getName() . '_' . $item->getName(); if ($name === $iname) { return $item->getDescription(); } @@ -104,9 +98,19 @@ class Fields extends \NDB_Page return ''; } - private function replaceDescription(string $name, string $description) : string { - $DB = $this->lorisinstance->getDatabaseConnection(); - $origdesc = $this->getOriginalDescription($name); + /** + * Override the description for field named $name and save it in the + * database. + * + * @param string $name The field being overridden + * @param string $description The new description + * + * @return string The status of the description after overriding + */ + private function _replaceDescription(string $name, string $description) : string + { + $DB = $this->lorisinstance->getDatabaseConnection(); + $origdesc = $this->_getOriginalDescription($name); if ($description == $origdesc) { $DB->delete( diff --git a/modules/dictionary/php/module.class.inc b/modules/dictionary/php/module.class.inc index eba42b1d0a3..a951b5795ea 100644 --- a/modules/dictionary/php/module.class.inc +++ b/modules/dictionary/php/module.class.inc @@ -60,49 +60,61 @@ class Module extends \Module /** * Return a list of modules and categories that this user - * has access to. + * has access to, stripping out modules that have no dictionary + * items that the user can access. * * This should not be used outside of this module, it's only - * to store shared code between different endpoints + * to store common shared code between different endpoints and + * must be public as a result of being used by different + * classes. + * + * @param \User $user The user accessing the dictionary + * @param \LORIS\LorisInstance $loris The LorisInstance which the dictionary + * is being accessed for + * @param ?string $formodule An optional module name to only + * retrieve the dictionary for a single + * module with the given name * * @return array */ - public function getUserModuleCategories(\User $user, \LORIS\LorisInstance $loris, ?string $formodule=null) : array - { - $modules = $loris->getActiveModules(); + public function getUserModuleCategories( + \User $user, + \LORIS\LorisInstance $loris, + ?string $formodule=null + ) : array { + $modules = $loris->getActiveModules(); $usermodules = []; - $dict = []; - $categories = []; + $dict = []; + $categories = []; $categoryitems = []; foreach ($modules as $module) { if ($formodule !== null && $module->getName() !== $formodule) { continue; } - if(!$module->hasAccess($user)) { + if (!$module->hasAccess($user)) { continue; } $mdict = $module->getDataDictionary($this->lorisinstance); $mname = $module->getName(); - if(count($mdict) > 0) { - $usermodules[] = $module; + if (count($mdict) > 0) { + $usermodules[] = $module; $categories[$mname] = []; - - foreach($mdict as $cat) { + foreach ($mdict as $cat) { $categories[$mname][$cat->getName()] = $cat->getDescription(); $categoryitems[] = [ - 'Module' => $module, + 'Module' => $module, 'Category' => $cat, ]; } } } return [ - 'Modules' => $usermodules, - 'Categories' => $categories, + 'Modules' => $usermodules, + 'Categories' => $categories, 'CategoryItems' => $categoryitems, ]; } diff --git a/modules/imaging_browser/php/module.class.inc b/modules/imaging_browser/php/module.class.inc index 80f4fee0236..2f44641ddb4 100644 --- a/modules/imaging_browser/php/module.class.inc +++ b/modules/imaging_browser/php/module.class.inc @@ -125,13 +125,25 @@ class Module extends \Module } return []; } + + /** + * {@inheritDoc} + * + * @param \LORIS\LorisInstance $loris The Loris instance from which the + * data dictionary for this module + * should be retrieved. + * + * @return iterable + */ public function getDataDictionary(\LORIS\LorisInstance $loris) : iterable { - $scope = new Scope(Scope::Session); - - $images = new \LORIS\Data\Dictionary\Category("Images", "Image Acquisitions"); + $scope = new Scope(Scope::Session); + $images = new \LORIS\Data\Dictionary\Category( + "Images", + "Image Acquisitions", + ); + $items = []; - $items = []; $scantypes = \Utility::getScanTypeList(); foreach ($scantypes as $ScanType) { $items[] = new DictionaryItem( From 5ce05035a715e304138a930380a7aacbcc64ba8d Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 24 Aug 2020 15:00:14 -0400 Subject: [PATCH 25/60] Restore datadict module from main branch --- modules/datadict/jsx/dataDictIndex.js | 170 +----------------- modules/datadict/php/datadict.class.inc | 23 +-- modules/datadict/php/datadictrow.class.inc | 30 +--- .../php/datadictrowprovisioner.class.inc | 87 +++------ modules/datadict/php/module.class.inc | 49 ----- 5 files changed, 33 insertions(+), 326 deletions(-) diff --git a/modules/datadict/jsx/dataDictIndex.js b/modules/datadict/jsx/dataDictIndex.js index 57040d584b5..f6e222add39 100644 --- a/modules/datadict/jsx/dataDictIndex.js +++ b/modules/datadict/jsx/dataDictIndex.js @@ -2,7 +2,6 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import Loader from 'Loader'; import FilterableDataTable from 'FilterableDataTable'; -import swal from 'sweetalert2'; import fetchDataStream from 'jslib/fetchDataStream'; @@ -37,8 +36,6 @@ class DataDictIndex extends Component { this.fetchData = this.fetchData.bind(this); this.formatColumn = this.formatColumn.bind(this); - this.editSwal = this.editSwal.bind(this); - this.updateFilter = this.updateFilter.bind(this); } /** @@ -59,111 +56,6 @@ class DataDictIndex extends Component { this.fetchData(); } - /** - * Update the filter to dynamically change the options in the - * 'Category' dropdown based on the selected module. - * - * @param {object} filter - The current filter state - */ - updateFilter(filter) { - if (filter.Module) { - this.setState({moduleFilter: filter.Module.value}); - } else { - this.setState({moduleFilter: ''}); - } - } - - /** - * Display a sweetalert popup to modify the row - * - * @param {object} row - The row being modified - * - * @return {function} callback function for react to activate swal - */ - editSwal(row) { - return () => { - swal.fire({ - title: 'Edit Description', - input: 'text', - inputValue: row.Description, - confirmButtonText: 'Modify', - showCancelButton: true, - inputValidator: (value) => { - if (!value) { - return 'Missing description'; - } - }, - }).then((result) => { - if (!result.value) { - return; - } - - const url = this.props.BaseURL - + '/datadict/fields/' - + encodeURI(row['Field Name']); - - // The fetch happens asyncronously, which means that the - // swal closes before it returns. We find the index that - // was being updated and aggressively update it, then - // re-update or reset it when the PUT request returns. - let i; - let odesc; - let ostat; - for (i = 0; i < this.state.data.Data.length; i++) { - if (this.state.data.Data[i][2] == row['Field Name']) { - // Store the original values in case the fetch - // fails and we need to restore them. - odesc = this.state.data.Data[i][3]; - ostat = this.state.data.Data[i][4]; - - // Aggressively update the state and assume - // it's been modified. - this.state.data.Data[i][3] = result.value; - this.state.data.Data[i][4] = 'Modified'; - - // Force a re-render - this.setState({state: this.state}); - break; - } - } - - fetch(url, { - method: 'PUT', - credentials: 'same-origin', - cache: 'no-cache', - body: result.value, - }).then((response) => { - if (!response.ok) { - // The response wasn't in the 200-299 range, - // so revert the update we did above and - // force a re-render. - this.state.data.Data[i][3] = odesc; - this.state.data.Data[i][4] = ostat; - - // Force a re-render - this.setState({state: this.state}); - return; - } - - // The response to the PUT request said we're - // good, but it's possible the status was changed - // back to the original. So update the status - // based on what the response said the value was. - this.state.data.Data[i][4] = response.headers.get('X-StatusDesc'); - this.setState({state: this.state}); - }).catch(() => { - // Something went wrong, restore the original - // status and description - this.state.data.Data[i][3] = odesc; - this.state.data.Data[i][4] = ostat; - - // Force a re-render - this.setState({state: this.state}); - }); - }); - }; - } - /** * Retrive data from the provided URL and save it in state */ @@ -220,6 +112,7 @@ class DataDictIndex extends Component { ); } + return {cell}; } /** @@ -240,17 +133,17 @@ class DataDictIndex extends Component { const options = this.state.fieldOptions; let fields = [ { - label: 'Module', + label: 'Source From', show: true, filter: { - name: 'Module', + name: 'Source From', type: 'select', - options: options.modules, + options: options.sourceFrom, }, }, { - label: 'Category', - show: false, + label: 'Name', + show: true, filter: { name: 'Source From', type: 'multiselect', @@ -258,10 +151,10 @@ class DataDictIndex extends Component { }, }, { - label: 'Field Name', + label: 'Source Field', show: true, filter: { - name: 'Name', + name: 'Source Field', type: 'text', }, }, @@ -275,7 +168,7 @@ class DataDictIndex extends Component { }, { label: 'Description Status', - show: false, + show: true, filter: { name: 'DescriptionStatus', type: 'select', @@ -286,50 +179,6 @@ class DataDictIndex extends Component { }, }, }, - { - label: 'Data Scope', - show: true, - filter: { - name: 'datascope', - type: 'select', - options: { - 'candidate': 'Candidate', - 'session': 'Session', - 'project': 'Project', - }, - }, - }, - { - label: 'Data Type', - show: true, - filter: { - name: 'datatype', - type: 'text', - }, - }, - { - label: 'Data Cardinality', - show: true, - filter: { - name: 'cardinality', - type: 'select', - options: { - 'unique': 'Unique', - 'single': 'Single', - 'optional': 'Optional', - 'many': 'Many', - }, - }, - }, - { - // We may or may not have an 8th column depending - // on type, which we need for formatting other columns. - // We don't show or display a filter because it's only - // valid for some data types. - label: 'Field Options', - show: false, - filter: null, - }, ]; return ( ); } diff --git a/modules/datadict/php/datadict.class.inc b/modules/datadict/php/datadict.class.inc index 30a370f0993..b680b8f7c64 100644 --- a/modules/datadict/php/datadict.class.inc +++ b/modules/datadict/php/datadict.class.inc @@ -11,7 +11,6 @@ * @link https://github.com/aces/Loris-Trunk */ namespace LORIS\datadict; -use \Psr\Http\Message\ServerRequestInterface; /** * Datadict module * @@ -101,7 +100,7 @@ class Datadict extends \DataFrameworkMenu */ public function getBaseDataProvisioner() : \LORIS\Data\Provisioner { - return new DataDictRowProvisioner($this->lorisinstance, $this->categoryitems); + return new DataDictRowProvisioner(); } /** @@ -144,24 +143,4 @@ class Datadict extends \DataFrameworkMenu { return false; } - - // FIXME: This should be moved to the parent class - public function getDataProvisionerWithFilters() : \LORIS\Data\Provisioner { - $prov = parent::getDataProvisionerWithFilters(); - return $prov->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); - } - - public function loadResources( - \User $user, ServerRequestInterface $request - ) : void { - $this->lorisinstance = $request->getAttribute("loris"); - $modulesandcats = $this->Module->getUserModuleCategories( - $user, - $this->lorisinstance, - ); - - $this->categoryitems = $modulesandcats['CategoryItems']; - $this->modules = $modulesandcats['Modules']; - $this->categories = $modulesandcats['Categories']; - } } diff --git a/modules/datadict/php/datadictrow.class.inc b/modules/datadict/php/datadictrow.class.inc index f111ed98b81..ca9c4ebe702 100644 --- a/modules/datadict/php/datadictrow.class.inc +++ b/modules/datadict/php/datadictrow.class.inc @@ -25,7 +25,7 @@ namespace LORIS\datadict; * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 * @link https://www.github.com/aces/Loris/ */ -class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\AccessibleResource +class DataDictRow implements \LORIS\Data\DataInstance { protected $DBRow; @@ -35,9 +35,8 @@ class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\Acce * @param array $row The row (in the same format as \Database::pselectRow * returns */ - public function __construct(\LORIS\Data\Dictionary\DictionaryItem $item, array $row) + public function __construct(array $row) { - $this->item = $item; $this->DBRow = $row; } @@ -50,29 +49,4 @@ class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\Acce { return $this->DBRow; } - - public function isAccessibleBy(\User $user) : bool { - return $this->item->isAccessibleBy($user); - } - - public function getCategory() : string { - return $this->DBRow['category']; - } - - public function getFieldName() : string { - return $this->DBRow['field']; - } - public function getDescription() : string { - return $this->DBRow['description']; - } - public function getScope() : \LORIS\Data\Scope { - return $this->DBRow['datascope']; - } - public function getDataType() : \LORIS\Data\Type{ - return $this->DBRow['type']; - } - public function getCardinality() : \LORIS\Data\Cardinality { - return $this->DBRow['cardinality']; - } - } diff --git a/modules/datadict/php/datadictrowprovisioner.class.inc b/modules/datadict/php/datadictrowprovisioner.class.inc index 9125f189b07..ac344df3711 100644 --- a/modules/datadict/php/datadictrowprovisioner.class.inc +++ b/modules/datadict/php/datadictrowprovisioner.class.inc @@ -28,74 +28,33 @@ namespace LORIS\datadict; * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 * @link https://www.github.com/aces/Loris/ */ -class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance +class DataDictRowProvisioner extends \LORIS\Data\Provisioners\DBRowProvisioner { /** * Create a DataDictRowProvisioner, which gets rows for * the datadict menu table. - * - * @param \LORIS\LorisInstance $loris The Loris instance whose - * dictionary should be retrieved - */ - function __construct(\LORIS\LorisInstance $loris, array $items) - { - $this->loris = $loris; - $this->items = $items; - } - - /** - * Get all dictionary instances, honouring parameter_type_override - * if applicable - * - * @return \Traversable */ - public function getAllInstances() : \Traversable + function __construct() { - $modules = $this->loris->getActiveModules(); - $DB = $this->loris->getDatabaseConnection(); - - $dict = []; - - $overrides = $DB->pselectWithIndexKey( - "SELECT Name, Description FROM parameter_type_override", - [], - "Name" + parent::__construct( + "SELECT DISTINCT + pt.sourceFrom as source_from, + pt.name as name, + pt.sourceField as source_field, + coalesce(pto.description,pt.description) as description, + CASE + WHEN COALESCE(pto.description,pt.description) = '' THEN 'Empty' + WHEN pto.name IS NOT NULL THEN 'Modified' + WHEN pto.name IS NULL THEN 'Unchanged' + END as description_status + FROM parameter_type pt + LEFT JOIN parameter_type_override pto USING (Name) + WHERE pt.Queryable=1 + ", + [] ); - - foreach ($this->items as $row) { - $module = $row['Module']; - $cat = $row['Category']; - foreach ($cat->getItems() as $item) { - $name = $item->getName(); - $desc = ''; - $status = 'Unchanged'; - if (isset($overrides[$name])) { - $desc = $overrides[$name]['Description']; - $status = 'Modified'; - } else { - $desc = $item->getDescription(); - } - - if ($desc == '') { - $status = 'Empty'; - } - $dict[] = $this->getInstance( - $item, - [ - 'module' => $module->getName(), - 'category' => $cat->getName(), - 'field' => $item->getName(), - 'description' => $desc, - 'description_status' => $status, - 'datascope' => $item->getScope(), - 'type' => $item->getDataType(), - 'cardinality' => $item->getCardinality(), - ] - ); - } - } - return new \ArrayIterator($dict); } + /** * Returns an instance of a DataDict object for a given * table row. @@ -104,12 +63,8 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance * * @return \LORIS\Data\DataInstance An instance representing this row. */ - public function getInstance(\LORIS\Data\Dictionary\DictionaryItem $item, array $row) : \LORIS\Data\DataInstance + public function getInstance($row) : \LORIS\Data\DataInstance { - $itype = $item->getDataType(); - if ($itype instanceof \LORIS\Data\Types\Enumeration) { - $row['options'] = $itype->getOptions(); - } - return new DataDictRow($item, $row); + return new DataDictRow($row); } } diff --git a/modules/datadict/php/module.class.inc b/modules/datadict/php/module.class.inc index 1c6c1a7b73f..87f902bf4c9 100644 --- a/modules/datadict/php/module.class.inc +++ b/modules/datadict/php/module.class.inc @@ -57,53 +57,4 @@ class Module extends \Module { return "Data Dictionary"; } - - /** - * Return a list of modules and categories that this user - * has access to. - * - * This should not be used outside of this module, it's only - * to store shared code between different endpoints - * - * @return array - */ - public function getUserModuleCategories(\User $user, \LORIS\LorisInstance $loris, ?string $formodule=null) : array - { - $modules = $loris->getActiveModules(); - $usermodules = []; - $dict = []; - $categories = []; - - $categoryitems = []; - foreach ($modules as $module) { - if ($formodule !== null && $module->getName() !== $formodule) { - continue; - } - if(!$module->hasAccess($user)) { - continue; - } - - $mdict = $module->getDataDictionary($this->lorisinstance); - $mname = $module->getName(); - - if(count($mdict) > 0) { - $usermodules[] = $module; - $categories[$mname] = []; - - - foreach($mdict as $cat) { - $categories[$mname][$cat->getName()] = $cat->getDescription(); - $categoryitems[] = [ - 'Module' => $module, - 'Category' => $cat, - ]; - } - } - } - return [ - 'Modules' => $usermodules, - 'Categories' => $categories, - 'CategoryItems' => $categoryitems, - ]; - } } From fff6d6cc09e5adbfcb25a0a7d10ecbe5a8765dc1 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 24 Aug 2020 15:06:15 -0400 Subject: [PATCH 26/60] Remove unused files --- modules/datadict/php/categories.class.inc | 73 ----------------------- php/libraries/NDB_Page.class.inc | 14 ----- 2 files changed, 87 deletions(-) delete mode 100644 modules/datadict/php/categories.class.inc diff --git a/modules/datadict/php/categories.class.inc b/modules/datadict/php/categories.class.inc deleted file mode 100644 index cfda0894f19..00000000000 --- a/modules/datadict/php/categories.class.inc +++ /dev/null @@ -1,73 +0,0 @@ -getQueryParams(); - $user = $request->getAttribute("user"); - if (isset($queryparams['module'])) { - return $this->moduleDictionary($request, $user, $queryparams['module']); - } - - $modulesandcats = $this->Module->getUserModuleCategories( - $user, - $request->getAttribute("loris"), - ); - - $modulesassoc = []; - - foreach($modulesandcats['Modules'] as $module) { - $modulesassoc[$module->getName()] = $module->getLongName(); - } - - return new \LORIS\Http\Response\JSON\OK( [ - 'modules' => $modulesassoc, - 'categories' => $modulesandcats['Categories'], - ]); - } - - public function moduleDictionary(ServerRequestInterface $request, \User $user, string $modulename) : ResponseInterface - { - - $loris = $this->lorisinstance; - foreach ($loris->getActiveModules() as $m) { - if ($m->getName() == $modulename) { - $module = $m; - break; - } - } - - if($module === null) { - return new \LORIS\Http\Response\JSON\NotFound('module not found'); - } - - $data = $this->Module->getUserModuleCategories($user, $loris, $modulename); - $prov = (new DataDictRowProvisioner($loris, $data['CategoryItems'])) - ->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); - - - $organized = []; - foreach ($prov->execute($user) as $row) { - $cat = $row->getCategory(); - if (!isset($organized[$cat])) { - $organized[$cat] = []; - } - $fieldname = $row->getFieldName(); - $datatype = $row->getDataType(); - $organized[$cat][$fieldname] = [ - 'description' => $row->getDescription(), - 'scope' => $row->getScope(), - 'type' => $row->getDataType(), - 'cardinality' => $row->getCardinality(), - ]; - if ($datatype instanceof \LORIS\Data\Types\Enumeration) { - $organized[$cat][$fieldname]['options'] = $datatype->getOptions(); - } - } - return new \LORIS\Http\Response\JSON\OK($organized); - } -} diff --git a/php/libraries/NDB_Page.class.inc b/php/libraries/NDB_Page.class.inc index 60f48683ce1..4e33659391c 100644 --- a/php/libraries/NDB_Page.class.inc +++ b/php/libraries/NDB_Page.class.inc @@ -728,20 +728,6 @@ class NDB_Page implements RequestHandlerInterface ->withBody(new \LORIS\Http\StringStream($this->display() ?? "")); } - /** - * This function can be overridden in a module's page to load the necessary - * resources to check the permissions of a user. - * - * @param User $user The user to load the resources for - * @param ServerRequestInterface $request The PSR15 Request being handled - * - * @return void - */ - public function loadResources( - \User $user, ServerRequestInterface $request - ) : void { - } - /** * Displays the form * From ae6522cb73885004b1570e4787dcec8c1471682d Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 24 Aug 2020 15:21:58 -0400 Subject: [PATCH 27/60] PHPCS --- php/libraries/Module.class.inc | 2 +- php/libraries/NDB_Page.class.inc | 2 +- src/Data/Cardinality.php | 1 + src/Data/Dictionary/DictionaryItem.php | 23 ++++++++++++++++--- src/Data/Filters/AccessibleResourceFilter.php | 1 - src/Data/Types/TimeType.php | 1 + 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/php/libraries/Module.class.inc b/php/libraries/Module.class.inc index 488a787f3b1..badb7e30568 100644 --- a/php/libraries/Module.class.inc +++ b/php/libraries/Module.class.inc @@ -349,7 +349,7 @@ abstract class Module extends \LORIS\Router\PrefixRouter $_REQUEST['subtest'] = $pagename; } - $page->loadResources($user, $request); + $page->loadResources($user, $request); if ($page->_hasAccess($user) !== true) { return (new \LORIS\Middleware\PageDecorationMiddleware( $user diff --git a/php/libraries/NDB_Page.class.inc b/php/libraries/NDB_Page.class.inc index 4e33659391c..374e1874870 100644 --- a/php/libraries/NDB_Page.class.inc +++ b/php/libraries/NDB_Page.class.inc @@ -202,7 +202,7 @@ class NDB_Page implements RequestHandlerInterface string $name, string $label, array $options, - array $attribs=array() + array $attribs=[] ) { $attribs = array_merge( ['class' => 'form-control input-sm'], diff --git a/src/Data/Cardinality.php b/src/Data/Cardinality.php index 05bf6c3e34c..1e07fd6380e 100644 --- a/src/Data/Cardinality.php +++ b/src/Data/Cardinality.php @@ -1,5 +1,6 @@ scope; } - public function getDataType() : \LORIS\Data\Type { + /** + * Return the data type for the data which this DictionaryItem + * describes. + * + * @return \LORIS\Data\Type + */ + public function getDataType() : \LORIS\Data\Type + { return $this->typ; } - public function getCardinality() : \LORIS\Data\Cardinality { + /** + * Return the data cardinality of this DictionaryItem. ie. for + * each entity of type Scope how many pieces of data should + * exist for this DictionaryItem. + * + * @return \LORIS\Data\Cardinality + */ + public function getCardinality() : \LORIS\Data\Cardinality + { return $this->cardinality; } @@ -144,7 +160,8 @@ public function getCardinality() : \LORIS\Data\Cardinality { * * @return bool */ - public function isAccessibleBy(\User $user): bool { + public function isAccessibleBy(\User $user): bool + { return true; } } diff --git a/src/Data/Filters/AccessibleResourceFilter.php b/src/Data/Filters/AccessibleResourceFilter.php index e6899893a07..ddc99596846 100644 --- a/src/Data/Filters/AccessibleResourceFilter.php +++ b/src/Data/Filters/AccessibleResourceFilter.php @@ -28,4 +28,3 @@ public function filter(\User $user, \Loris\Data\DataInstance $resource) : bool return $resource->isAccessibleBy($user); } } - diff --git a/src/Data/Types/TimeType.php b/src/Data/Types/TimeType.php index c4a89283f00..005150023e3 100644 --- a/src/Data/Types/TimeType.php +++ b/src/Data/Types/TimeType.php @@ -1,5 +1,6 @@ Date: Wed, 26 Aug 2020 12:04:48 -0400 Subject: [PATCH 28/60] More phan --- .../php/datadictrowprovisioner.class.inc | 3 +-- modules/dictionary/php/fields.class.inc | 3 +-- modules/dictionary/php/module.class.inc | 1 - modules/dictionary/test/datadictTest.php | 25 +++++++++---------- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/modules/dictionary/php/datadictrowprovisioner.class.inc b/modules/dictionary/php/datadictrowprovisioner.class.inc index d479a0a7497..add15259afa 100644 --- a/modules/dictionary/php/datadictrowprovisioner.class.inc +++ b/modules/dictionary/php/datadictrowprovisioner.class.inc @@ -35,8 +35,7 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance */ public function getAllInstances() : \Traversable { - $modules = $this->loris->getActiveModules(); - $DB = $this->loris->getDatabaseConnection(); + $DB = $this->loris->getDatabaseConnection(); $dict = []; diff --git a/modules/dictionary/php/fields.class.inc b/modules/dictionary/php/fields.class.inc index a34d7e6c82c..f4c295a1ae6 100644 --- a/modules/dictionary/php/fields.class.inc +++ b/modules/dictionary/php/fields.class.inc @@ -49,8 +49,7 @@ class Fields extends \NDB_Page // delegated to this class.. throw new \LorisException("Invalid internal LORIS state"); } - $name = substr($path, strpos($path, "fields/") + strlen("fields/")); - $origdesc = $this->_getOriginalDescription($name); + $name = substr($path, strpos($path, "fields/") + strlen("fields/")); switch ($request->getMethod()) { case 'PUT': $status = $this->_replaceDescription( diff --git a/modules/dictionary/php/module.class.inc b/modules/dictionary/php/module.class.inc index a951b5795ea..189ecfa3ac8 100644 --- a/modules/dictionary/php/module.class.inc +++ b/modules/dictionary/php/module.class.inc @@ -84,7 +84,6 @@ class Module extends \Module ) : array { $modules = $loris->getActiveModules(); $usermodules = []; - $dict = []; $categories = []; $categoryitems = []; diff --git a/modules/dictionary/test/datadictTest.php b/modules/dictionary/test/datadictTest.php index bc710b752c5..bfb7e005c59 100644 --- a/modules/dictionary/test/datadictTest.php +++ b/modules/dictionary/test/datadictTest.php @@ -25,22 +25,21 @@ * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 * @link https://github.com/aces/Loris */ -class DatadictTestIntegrationTest extends LorisIntegrationTest +class DictionaryTestIntegrationTest extends LorisIntegrationTest { /** * UI elements and locations * breadcrumb - 'Access Profile' * Table headers */ - private $_loadingUI - = [ - 'Data Dictionary' => '#bc2 > a:nth-child(2) > div', - 'Source From' => '#dynamictable > thead > tr > th:nth-child(2)', - 'Name' => '#dynamictable > thead > tr > th:nth-child(3)', - 'Source Field' => '#dynamictable > thead > tr > th:nth-child(4)', - 'Description' => '#dynamictable > thead > tr > th:nth-child(5)', - 'Description Status' => '#dynamictable > thead > tr > th:nth-child(6)', - ]; + protected $loadingUI = [ + 'Data Dictionary' => '#bc2 > a:nth-child(2) > div', + 'Source From' => '#dynamictable > thead > tr > th:nth-child(2)', + 'Name' => '#dynamictable > thead > tr > th:nth-child(3)', + 'Source Field' => '#dynamictable > thead > tr > th:nth-child(4)', + 'Description' => '#dynamictable > thead > tr > th:nth-child(5)', + 'Description Status' => '#dynamictable > thead > tr > th:nth-child(6)', + ]; /** * Inserting testing data @@ -83,9 +82,9 @@ function tearDown() * * @return void */ - function testDatadictDoespageLoad() + function testDictionaryDoespageLoad() { - $this->webDriver->get($this->url . "/datadict/"); + $this->webDriver->get($this->url . "/dictionary/"); $this->webDriver->wait(120, 1000)->until( WebDriverExpectedCondition::presenceOfElementLocated( @@ -105,7 +104,7 @@ function testDatadictDoespageLoad() */ function testPageUIs() { - $this->safeGet($this->url . "/datadict/"); + $this->safeGet($this->url . "/dictionary/"); foreach ($this->_loadingUI as $key => $value) { $text = $this->safeFindElement( WebDriverBy::cssSelector($value) From 6f9c2264b78442b75ebd241769ea812609f9dd64 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 26 Aug 2020 12:57:10 -0400 Subject: [PATCH 29/60] More phan phun --- modules/dictionary/php/categories.class.inc | 4 +++- modules/dictionary/php/datadictrow.class.inc | 4 ++-- modules/imaging_browser/php/module.class.inc | 8 ++++---- php/libraries/NDB_BVL_Instrument_LINST.class.inc | 15 +++++++-------- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/modules/dictionary/php/categories.class.inc b/modules/dictionary/php/categories.class.inc index 93a20704d37..ceaad2108b5 100644 --- a/modules/dictionary/php/categories.class.inc +++ b/modules/dictionary/php/categories.class.inc @@ -64,7 +64,9 @@ class Categories extends \NDB_Page \User $user, string $modulename ) : ResponseInterface { - $loris = $this->lorisinstance; + $loris = $this->lorisinstance; + $module = null; + foreach ($loris->getActiveModules() as $m) { if ($m->getName() == $modulename) { $module = $m; diff --git a/modules/dictionary/php/datadictrow.class.inc b/modules/dictionary/php/datadictrow.class.inc index 6571d822527..d57e632a847 100644 --- a/modules/dictionary/php/datadictrow.class.inc +++ b/modules/dictionary/php/datadictrow.class.inc @@ -79,9 +79,9 @@ class DataDictRow implements \LORIS\Data\DataInstance, /** * Return the module category to which the DictionaryItem belongs * - * @return string + * @return Category */ - public function getCategory() : string + public function getCategory() : Category { return $this->itemcategory; } diff --git a/modules/imaging_browser/php/module.class.inc b/modules/imaging_browser/php/module.class.inc index 2f44641ddb4..be4b32f1543 100644 --- a/modules/imaging_browser/php/module.class.inc +++ b/modules/imaging_browser/php/module.class.inc @@ -133,11 +133,11 @@ class Module extends \Module * data dictionary for this module * should be retrieved. * - * @return iterable + * @return \LORIS\Data\Dictionary\Category[] */ public function getDataDictionary(\LORIS\LorisInstance $loris) : iterable { - $scope = new Scope(Scope::Session); + $scope = new Scope(Scope::SESSION); $images = new \LORIS\Data\Dictionary\Category( "Images", "Image Acquisitions", @@ -151,7 +151,7 @@ class Module extends \Module "$ScanType acquisition location", $scope, new \LORIS\Data\Types\URI(), - new Cardinality(Cardinality::Many), + new Cardinality(Cardinality::MANY), ); // TODO: Investigate adding a file scope instead of having this apply // on a session scope with a Many cardinality. @@ -160,7 +160,7 @@ class Module extends \Module "Quality Control status for $ScanType acquisition", $scope, new \LORIS\Data\Types\Enumeration("Pass", "Fail"), - new Cardinality(Cardinality::Many), + new Cardinality(Cardinality::MANY), ); } $images = $images->withItems($items); diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index dcbe4e05e45..0b9e6bea785 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -469,7 +469,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument function loadInstrumentFile(string $filename, bool $base64 = false): void { $scope = new Scope(Scope::SESSION); - if (file_exists($filename) || $base64 === true) { $this->InstrumentType = 'LINST'; @@ -564,21 +563,21 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument 'Date of Administration', $scope, new \LORIS\Data\Types\DateType(), - new Cardinality(Cardinality::Single), + new Cardinality(Cardinality::SINGLE), ), new DictionaryItem( 'Candidate_age', 'Candidate Age (Months)', $scope, new \LORIS\Data\Types\Interval(), - new Cardinality(Cardinality::Single), + new Cardinality(Cardinality::SINGLE), ), new DictionaryItem( 'Window_Difference', 'Window difference from test battery (days)', $scope, new \LORIS\Data\Types\Interval(), - new Cardinality(Cardinality::Single), + new Cardinality(Cardinality::SINGLE), ), new DictionaryItem( 'Examiner', @@ -586,7 +585,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $scope, // Should this be an enum of examiners? new StringType(255), - new Cardinality(Cardinality::Single), + new Cardinality(Cardinality::SINGLE), ), ] ); @@ -663,7 +662,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $pieces[2], $scope, new StringType(255), - new Cardinality(Cardinality::Single), + new Cardinality(Cardinality::SINGLE), ); } $this->dictionary[] = new DictionaryItem( @@ -695,7 +694,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $pieces[2], $scope, new StringType(), - new Cardinality(Cardinality::Single), + new Cardinality(Cardinality::SINGLE), ); } $this->dictionary[] = new DictionaryItem( @@ -730,7 +729,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $pieces[2], $scope, new DateType(), - new Cardinality(Cardinality::Single), + new Cardinality(Cardinality::SINGLE), ); $this->dictionary[] = new DictionaryItem( From 11a85697eb5cbce7cccc3ffab71b37a236cbff59 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 26 Aug 2020 13:06:17 -0400 Subject: [PATCH 30/60] Update README --- modules/dictionary/README.md | 54 +++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/modules/dictionary/README.md b/modules/dictionary/README.md index 2c3e8d5a4dd..ad81f81bf2f 100644 --- a/modules/dictionary/README.md +++ b/modules/dictionary/README.md @@ -2,21 +2,18 @@ ## Purpose -The data dictionary module is used for viewing and modifying the -LORIS data dictionary descriptions. +The dictionary module is used for viewing and modifying the +LORIS data dictionary for installed and active modules. ## Scope -The module displays and manages the data dictionary for fields -already stored in the LORIS `parameter_type` table. This table is -generally autopopulated and provides a dictionary for imaging headers -(populated by the imaging pipeline) and behavioural instrument -(populated by the `lorisform_parser.php` script). +The module displays and manages the data dictionary for modules +which provide a dictionary, and allows users to override the +descriptions provided. It does not provide a way to enter new fields into the data dictionary, -or describe the data dictionary for arbitrary SQL fields which are -not stored in the `parameter_type` table, such as instrument flags -or candidate/session data. +doing so requires updating or creating a new module which provides +a dictionary. ## Permission @@ -28,24 +25,29 @@ modify the description of, rows in the data dictionary. ## Configuration -The `parameter_type` table must be populated to use this module. -The data comes from two sources: +None -1. The LORIS imaging pipeline scripts, which must be setup separately -2. The `tools/data_dictionary_builder.php` script, which loads the - behavioural data dictionary based on the `ip_output.txt` file created - by `tools/lorisform_parser.php` +## Notes -## Interactions with LORIS +The field names should be unique (see interactions below). + +Instruments must implement the getDataDictionary method. The +`LorisFormDictionaryImpl` trait can be used to implement the +dictionary in the same way that the `data_dictionary_builder` +script populates the `parameter_type` table for the `data_dict` +module. However, this is done on a heuristic best-effort basis. +Data can be described more accurately described by implementing +the function manually. -The content for the data dictionary module comes from the -`parameter_type` table. +## Interactions with LORIS -Modified entries are saved in the separate `parameter_type_override` -table, and the "data dictionary" used by LORIS is the coalesce of -the two tables, with `parameter_type_override` taking priority. -This allows the `parameter_type` table itself to be regenerated -when instruments change without losing the user modified values. +The `dictionary` module uses the same override descriptions as +the `data_dict` module so that users do not need to modify the +description twice as the dictionary is migrated to being managed +by modules. This means that the name of fields should be unique +across LORIS until the `data_dict` module is retired. -The data query module uses the customized data dictionary descriptions -from this module. +Modules such as the data query module may use this module as +an API to get a list of fields to be displayed and determine +their scope, cardinality, and (possibly overriddens) description for +context sensitive display purposes. From 70f0de919e5f069f1655e92ff1cd4fceaa6b8dee Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 26 Aug 2020 13:38:22 -0400 Subject: [PATCH 31/60] Move module dictionary to own endpoint --- modules/dictionary/php/categories.class.inc | 57 ----------- modules/dictionary/php/module.class.inc | 12 +++ modules/dictionary/php/moduledict.class.inc | 104 ++++++++++++++++++++ 3 files changed, 116 insertions(+), 57 deletions(-) create mode 100644 modules/dictionary/php/moduledict.class.inc diff --git a/modules/dictionary/php/categories.class.inc b/modules/dictionary/php/categories.class.inc index ceaad2108b5..14a448acfa0 100644 --- a/modules/dictionary/php/categories.class.inc +++ b/modules/dictionary/php/categories.class.inc @@ -24,9 +24,6 @@ class Categories extends \NDB_Page { $queryparams = $request->getQueryParams(); $user = $request->getAttribute("user"); - if (isset($queryparams['module'])) { - return $this->moduleDictionary($request, $user, $queryparams['module']); - } // getUserModuleCategories enforces permissions and strips out modules // that don't have any dictionary. @@ -48,58 +45,4 @@ class Categories extends \NDB_Page ] ); } - - /** - * TODO: Move this - * - * @param ServerRequestInterface $request The request - * @param \User $user The user accessing the dictionary - * @param string $modulename The module whose dictionary should - * be retrieved - * - * @return ResponseInterface - */ - public function moduleDictionary( - ServerRequestInterface $request, - \User $user, - string $modulename - ) : ResponseInterface { - $loris = $this->lorisinstance; - $module = null; - - foreach ($loris->getActiveModules() as $m) { - if ($m->getName() == $modulename) { - $module = $m; - break; - } - } - - if ($module === null) { - return new \LORIS\Http\Response\JSON\NotFound('module not found'); - } - - $data = $this->Module->getUserModuleCategories($user, $loris, $modulename); - $prov = (new DataDictRowProvisioner($loris, $data['CategoryItems'])) - ->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); - - $organized = []; - foreach ($prov->execute($user) as $row) { - $cat = $row->getCategory(); - if (!isset($organized[$cat])) { - $organized[$cat] = []; - } - $fieldname = $row->getFieldName(); - $datatype = $row->getDataType(); - $organized[$cat][$fieldname] = [ - 'description' => $row->getDescription(), - 'scope' => $row->getScope(), - 'type' => $row->getDataType(), - 'cardinality' => $row->getCardinality(), - ]; - if ($datatype instanceof \LORIS\Data\Types\Enumeration) { - $organized[$cat][$fieldname]['options'] = $datatype->getOptions(); - } - } - return new \LORIS\Http\Response\JSON\OK($organized); - } } diff --git a/modules/dictionary/php/module.class.inc b/modules/dictionary/php/module.class.inc index 189ecfa3ac8..52f4e2e4224 100644 --- a/modules/dictionary/php/module.class.inc +++ b/modules/dictionary/php/module.class.inc @@ -117,4 +117,16 @@ class Module extends \Module 'CategoryItems' => $categoryitems, ]; } + + /** + * Overload loadPage to delegate 'module' to 'moduledict' in order to + * handle the URL `dictionary/module/$name`, since the Module class name + * is already used for the module descriptor. + */ + public function loadPage(string $page) { + if ($page === 'module') { + return $this->loadPage('moduledict'); + } + return parent::loadPage($page); + } } diff --git a/modules/dictionary/php/moduledict.class.inc b/modules/dictionary/php/moduledict.class.inc new file mode 100644 index 00000000000..381f508d534 --- /dev/null +++ b/modules/dictionary/php/moduledict.class.inc @@ -0,0 +1,104 @@ +getAttribute("user"); + if ($user === null && !($user instanceof \User)) { + return new \LORIS\Http\Response\JSON\InternalServerError( + "No valid user" + ); + } + if (!($this->_hasAccess($user))) { + return new \LORIS\Http\Response\JSON\Forbidden(); + } + + $path = $request->getURI()->getPath(); + if (strpos($path, "module/") === false) { + // if the path doesn't contain "fields" we shouldn't have + // delegated to this class.. + throw new \LorisException("Invalid internal LORIS state"); + } + + $name = substr($path, strpos($path, "module/") + strlen("module/")); + return $this->moduleDictionary($request, $user, $name); + } + + /** + * Return a response corresponding to the dictionary for the module named + * modulename + * + * @param ServerRequestInterface $request The request + * @param \User $user The user accessing the dictionary + * @param string $modulename The module whose dictionary should + * be retrieved + * + * @return ResponseInterface + */ + public function moduleDictionary( + ServerRequestInterface $request, + \User $user, + string $modulename + ) : ResponseInterface { + $loris = $this->lorisinstance; + $module = null; + + foreach ($loris->getActiveModules() as $m) { + if ($m->getName() == $modulename) { + $module = $m; + break; + } + } + + if ($module === null) { + return new \LORIS\Http\Response\JSON\NotFound('module not found'); + } + + $data = $this->Module->getUserModuleCategories($user, $loris, $modulename); + $prov = (new DataDictRowProvisioner($loris, $data['CategoryItems'])) + ->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); + + $organized = []; + foreach ($prov->execute($user) as $row) { + $cat = $row->getCategory()->getName(); + if (!isset($organized[$cat])) { + $organized[$cat] = []; + } + $fieldname = $row->getFieldName(); + $datatype = $row->getDataType(); + $organized[$cat][$fieldname] = [ + 'description' => $row->getDescription(), + 'scope' => $row->getScope(), + 'type' => $row->getDataType(), + 'cardinality' => $row->getCardinality(), + ]; + if ($datatype instanceof \LORIS\Data\Types\Enumeration) { + $organized[$cat][$fieldname]['options'] = $datatype->getOptions(); + } + } + return new \LORIS\Http\Response\JSON\OK($organized); + } + + +} From 094e7cca50c4baf7cd0850f00568764a4c4a30d0 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 26 Aug 2020 14:06:35 -0400 Subject: [PATCH 32/60] Update help text --- modules/dictionary/help/dictionary.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 modules/dictionary/help/dictionary.md diff --git a/modules/dictionary/help/dictionary.md b/modules/dictionary/help/dictionary.md new file mode 100644 index 00000000000..e18ce18ce61 --- /dev/null +++ b/modules/dictionary/help/dictionary.md @@ -0,0 +1,17 @@ +# Data Dictionary + +This module houses definitions or descriptions for the data fields in LORIS. + +Use the *Selection Filter* section to search for specific fields. Your results will be displayed in the data table below. + +Click on the pencil icon to override the *Description* of a field - this should be done with caution. When a field description is modified, the new description is used in the Data Query Tool (after it is refreshed), by users who are selecting data fields to analyze or download. The modified field description will not be visible in the instrument form into which a user or survey participant enters data. + +The *Data Scope* column represents the scope that the dictionary item corresponds to and can be +either "candidate" or "session". + +The *Data Cardinality* describes how many items of the dictionary type may exist per scope. +It may be one of: +- **Unique** -- there is a single value across all entities of the scope (ie. an unique candidate identifier for a candidate or a visit label for a session) +- **Single** -- There is exactly one value per entity of the scope +- **Optional** -- There may be one or zero values per entity of the scope +- **Many** -- There may be zero or more values associated with entities of the scope From 0c22ab7df81b50278ad887a40eb552dd22a4ee6e Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 26 Aug 2020 14:10:24 -0400 Subject: [PATCH 33/60] Update tests --- modules/dictionary/test/datadictTest.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/dictionary/test/datadictTest.php b/modules/dictionary/test/datadictTest.php index bfb7e005c59..1799768cd0f 100644 --- a/modules/dictionary/test/datadictTest.php +++ b/modules/dictionary/test/datadictTest.php @@ -33,12 +33,13 @@ class DictionaryTestIntegrationTest extends LorisIntegrationTest * Table headers */ protected $loadingUI = [ - 'Data Dictionary' => '#bc2 > a:nth-child(2) > div', - 'Source From' => '#dynamictable > thead > tr > th:nth-child(2)', - 'Name' => '#dynamictable > thead > tr > th:nth-child(3)', - 'Source Field' => '#dynamictable > thead > tr > th:nth-child(4)', - 'Description' => '#dynamictable > thead > tr > th:nth-child(5)', - 'Description Status' => '#dynamictable > thead > tr > th:nth-child(6)', + 'Data Dictionary' => '#bc2 > a:nth-child(2) > div', + 'Module ' => '#dynamictable > thead > tr > th:nth-child(2)', + 'Field Name' => '#dynamictable > thead > tr > th:nth-child(3)', + 'Description' => '#dynamictable > thead > tr > th:nth-child(4)', + 'Data Scope' => '#dynamictable > thead > tr > th:nth-child(5)', + 'Data Type' => '#dynamictable > thead > tr > th:nth-child(6)', + 'Data Cardinality' => '#dynamictable > thead > tr > th:nth-child(7)', ]; /** @@ -105,7 +106,7 @@ function testDictionaryDoespageLoad() function testPageUIs() { $this->safeGet($this->url . "/dictionary/"); - foreach ($this->_loadingUI as $key => $value) { + foreach ($this->loadingUI as $key => $value) { $text = $this->safeFindElement( WebDriverBy::cssSelector($value) )->getText(); From b1a7ff9c6363e37c10b5a882757a7ad026299504 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 26 Aug 2020 14:12:38 -0400 Subject: [PATCH 34/60] Fix type --- modules/dictionary/help/datadict.md | 8 -------- modules/dictionary/php/datadictrow.class.inc | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 modules/dictionary/help/datadict.md diff --git a/modules/dictionary/help/datadict.md b/modules/dictionary/help/datadict.md deleted file mode 100644 index 5c3f5a5e4c0..00000000000 --- a/modules/dictionary/help/datadict.md +++ /dev/null @@ -1,8 +0,0 @@ -# Data Dictionary - -This module houses definitions or descriptions for the data fields in instrument forms loaded in your LORIS. - -Use the *Selection Filter* section to search for specific fields. Your results will be displayed in the data table below. - -Note that you can directly edit any cell in the *Description* column - this should be done with caution. -When a field description is modified, the new description is used in the Data Query Tool (after it is refreshed), by users who are selecting data fields to analyze or download. The modified field description will not be visible in the instrument form into which a user or survey participant enters data. diff --git a/modules/dictionary/php/datadictrow.class.inc b/modules/dictionary/php/datadictrow.class.inc index d57e632a847..a4ab0dc3ebf 100644 --- a/modules/dictionary/php/datadictrow.class.inc +++ b/modules/dictionary/php/datadictrow.class.inc @@ -51,7 +51,7 @@ class DataDictRow implements \LORIS\Data\DataInstance, 'description' => $this->desc, 'description_status' => $this->status, 'datascope' => $this->getScope(), - 'type' => $this->getScope(), + 'type' => $this->getDataType(), 'cardinality' => $this->getCardinality(), ]; From 05ee648a336910d1ae7927ba5a088810ff3ad345 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 26 Aug 2020 14:15:43 -0400 Subject: [PATCH 35/60] Remove duplicated function which was merged separately --- jsx/FilterableDataTable.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/jsx/FilterableDataTable.js b/jsx/FilterableDataTable.js index 7f340e07a51..c4542c9c929 100644 --- a/jsx/FilterableDataTable.js +++ b/jsx/FilterableDataTable.js @@ -95,34 +95,6 @@ class FilterableDataTable extends Component { this.updateFilters({}); } - /** - * Returns filters which aren't in an invalid - * state - * - * @return {object} - */ - validFilters() { - let filters = {}; - this.props.fields.forEach((field) => { - const filtername = field.filter.name; - const filterval = this.state.filter[filtername]; - if (!this.state.filter[filtername]) { - return; - } - - if (field.filter.type !== 'select') { - filters[filtername] = filterval; - return; - } - - if (!(filterval.value in field.filter.options)) { - return; - } - filters[filtername] = filterval; - }); - return filters; - } - /** * Returns the filter state, with filters that are * set to an invalid option removed from the returned From 4fa01098cf0bb00de1153217ed02ecad9d516019 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 26 Aug 2020 14:16:40 -0400 Subject: [PATCH 36/60] Update gitignore --- modules/dictionary/.gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/dictionary/.gitignore b/modules/dictionary/.gitignore index 076131bc368..d5a65bf3098 100644 --- a/modules/dictionary/.gitignore +++ b/modules/dictionary/.gitignore @@ -1,2 +1 @@ -js/columnFormatter.js -js/dataDictIndex.js +js/* From ec757d1fdbcede1908907cc62c106eb19809151f Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 26 Aug 2020 14:18:24 -0400 Subject: [PATCH 37/60] Update comments --- modules/dictionary/php/dictionary.class.inc | 13 +++---------- modules/dictionary/php/fields.class.inc | 19 ++++++++++--------- modules/dictionary/php/moduledict.class.inc | 9 +++------ 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/modules/dictionary/php/dictionary.class.inc b/modules/dictionary/php/dictionary.class.inc index 9a94fa342d7..7a1bec8d2e1 100644 --- a/modules/dictionary/php/dictionary.class.inc +++ b/modules/dictionary/php/dictionary.class.inc @@ -6,22 +6,15 @@ namespace LORIS\dictionary; use \Psr\Http\Message\ServerRequestInterface; /** - * Datadict module + * Handle the main index page of the dictionary module * - * PHP version 7 - * - * @category Datadict - * @package Main - * @author Ted Strauss - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://github.com/aces/Loris-Trunk + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 */ class Dictionary extends \DataFrameworkMenu { /** - * Overloading this method to allow access to site users (their own site - * only) and users w/ multisite privs + * {@inheritDoc} * * @param \User $user The user whose access is being checked * diff --git a/modules/dictionary/php/fields.class.inc b/modules/dictionary/php/fields.class.inc index f4c295a1ae6..82b3d4d5da1 100644 --- a/modules/dictionary/php/fields.class.inc +++ b/modules/dictionary/php/fields.class.inc @@ -4,7 +4,8 @@ namespace LORIS\dictionary; use \Psr\Http\Message\ServerRequestInterface; use \Psr\Http\Message\ResponseInterface; /** - * Datadict module + * The dictionary/fields endpoint handles requests to update a specific + * field description * * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 */ @@ -23,14 +24,14 @@ class Fields extends \NDB_Page return $user->hasPermission('data_dict_edit'); } - /** - * The modules class overrides the default behaviour to handle PATCH - * requests for module_manager/modules/$modulename. - * - * @param ServerRequestInterface $request The incoming PSR7 request - * - * @return ResponseInterface - */ + /** + * The modules class overrides the default behaviour to handle PUT + * requests for the fields endpoint. + * + * @param ServerRequestInterface $request The incoming PSR7 request + * + * @return ResponseInterface + */ public function handle(ServerRequestInterface $request) : ResponseInterface { $user = $request->getAttribute("user"); diff --git a/modules/dictionary/php/moduledict.class.inc b/modules/dictionary/php/moduledict.class.inc index 381f508d534..0a20a8cc420 100644 --- a/modules/dictionary/php/moduledict.class.inc +++ b/modules/dictionary/php/moduledict.class.inc @@ -4,9 +4,7 @@ use \Psr\Http\Message\ServerRequestInterface; use \Psr\Http\Message\ResponseInterface; /** - * Categories page returns a list of valid module and their categories - * which both have a valid data dictionary and the user accessing the - * page has access to. + * The ModuleDict page returns the dictionary for a single module. * * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 */ @@ -15,8 +13,7 @@ class ModuleDict extends \NDB_Page protected $module; /** - * Override the hndle to return a list of valid modules and categories - * that the user has access to. + * Override the hndle to return a dictionary for a given module * * @param ServerRequestInterface $request the incoming PSR7 request * @@ -36,7 +33,7 @@ class ModuleDict extends \NDB_Page $path = $request->getURI()->getPath(); if (strpos($path, "module/") === false) { - // if the path doesn't contain "fields" we shouldn't have + // if the path doesn't contain "module" we shouldn't have // delegated to this class.. throw new \LorisException("Invalid internal LORIS state"); } From 40439c70d284404287d9e1e24c18426736e13220 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 9 Dec 2020 10:55:48 -0500 Subject: [PATCH 38/60] Remove unnecessary changes --- php/libraries/NDB_Page.class.inc | 28 ++++++++++++++-------------- webpack.config.js | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/php/libraries/NDB_Page.class.inc b/php/libraries/NDB_Page.class.inc index 374e1874870..4c0aff64b81 100644 --- a/php/libraries/NDB_Page.class.inc +++ b/php/libraries/NDB_Page.class.inc @@ -728,6 +728,20 @@ class NDB_Page implements RequestHandlerInterface ->withBody(new \LORIS\Http\StringStream($this->display() ?? "")); } + /** + * This function can be overridden in a module's page to load the necessary + * resources to check the permissions of a user. + * + * @param User $user The user to load the resources for + * @param ServerRequestInterface $request The PSR15 Request being handled + * + * @return void + */ + public function loadResources( + \User $user, ServerRequestInterface $request + ) : void { + } + : /** * Displays the form * @@ -927,18 +941,4 @@ class NDB_Page implements RequestHandlerInterface { return json_encode(['error' => 'Not implemented']); } - - /** - * This function can be overridden in a module's page to load the necessary - * resources to check the permissions of a user. - * - * @param User $user The user to load the resources for - * @param ServerRequestInterface $request The PSR15 Request being handled - * - * @return void - */ - public function loadResources( - \User $user, ServerRequestInterface $request - ) : void { - } } diff --git a/webpack.config.js b/webpack.config.js index 5ca35192c57..650bddd6988 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -229,6 +229,7 @@ const config = [ 'onLoad', 'candidateListIndex', ]), + lorisModule('datadict', ['dataDictIndex']), lorisModule('data_release', [ 'dataReleaseIndex', ]), @@ -240,6 +241,7 @@ const config = [ 'react.sidebar', 'react.tabs', ]), + lorisModule('dictionary', ['dataDictIndex']), lorisModule('dqt', [ 'components/expansionpanels', 'components/searchabledropdown', @@ -288,8 +290,6 @@ const config = [ lorisModule('server_processes_manager', ['server_processes_managerIndex']), lorisModule('instruments', ['CandidateInstrumentList']), lorisModule('candidate_profile', ['CandidateInfo']), - lorisModule('datadict', ['dataDictIndex']), - lorisModule('dictionary', ['dataDictIndex']), ]; // Support project overrides From 14d475bbc8f73432a613c35f0042c25a9305ec32 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 9 Dec 2020 10:56:23 -0500 Subject: [PATCH 39/60] Fix file rename that was somehow lost in a rebase --- src/Data/Types/{Interval.php => Duration.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Data/Types/{Interval.php => Duration.php} (100%) diff --git a/src/Data/Types/Interval.php b/src/Data/Types/Duration.php similarity index 100% rename from src/Data/Types/Interval.php rename to src/Data/Types/Duration.php From 942a2b5ec4231a144b4f2eaeda0ec7f3f1fe1e65 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 9 Dec 2020 11:05:10 -0500 Subject: [PATCH 40/60] Remove invalid token Not sure how that got there --- php/libraries/NDB_Page.class.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/libraries/NDB_Page.class.inc b/php/libraries/NDB_Page.class.inc index 4c0aff64b81..15317724039 100644 --- a/php/libraries/NDB_Page.class.inc +++ b/php/libraries/NDB_Page.class.inc @@ -741,7 +741,7 @@ class NDB_Page implements RequestHandlerInterface \User $user, ServerRequestInterface $request ) : void { } - : + /** * Displays the form * From acd47affa5d43194e1f8c3bf8952fca54562f344 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 17 Mar 2021 10:29:57 -0400 Subject: [PATCH 41/60] Fix badly rebased DictionaryItem --- src/Data/Dictionary/DictionaryItem.php | 49 -------------------------- 1 file changed, 49 deletions(-) diff --git a/src/Data/Dictionary/DictionaryItem.php b/src/Data/Dictionary/DictionaryItem.php index 8c4da059e39..44015061007 100644 --- a/src/Data/Dictionary/DictionaryItem.php +++ b/src/Data/Dictionary/DictionaryItem.php @@ -6,10 +6,6 @@ use \LORIS\Data\Type; use \LORIS\Data\Cardinality; -use \LORIS\Data\Scope; -use \LORIS\Data\Type; -use \LORIS\Data\Cardinality; - /** * A DictionaryItem represents a description of a type of data * managed by LORIS. @@ -119,49 +115,4 @@ public function isAccessibleBy(\User $user): bool { return true; } - - public function getScope() : Scope { - return $this->scope; - } - - /** - * Return the data type for the data which this DictionaryItem - * describes. - * - * @return \LORIS\Data\Type - */ - public function getDataType() : \LORIS\Data\Type - { - return $this->typ; - } - - /** - * Return the data cardinality of this DictionaryItem. ie. for - * each entity of type Scope how many pieces of data should - * exist for this DictionaryItem. - * - * @return \LORIS\Data\Cardinality - */ - public function getCardinality() : \LORIS\Data\Cardinality - { - return $this->cardinality; - } - - /** - * The DictionaryItem instance implements the AccessibleResource - * interface in order to make it possible to restrict items per - * user. However, by default DictionaryItems are accessible by - * all users. In order to restrict access to certain items, a - * module would need to extend this class and override the - * isAccessibleBy method with its prefered business logic. - * - * @param \User $user The user whose access should be - * validated - * - * @return bool - */ - public function isAccessibleBy(\User $user): bool - { - return true; - } } From eef9559aea77fe988b22d4e0239bbad30ac26bfb Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 17 Mar 2021 10:31:14 -0400 Subject: [PATCH 42/60] More bad rebasing --- modules/datadict/jsx/dataDictIndex.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/datadict/jsx/dataDictIndex.js b/modules/datadict/jsx/dataDictIndex.js index f6e222add39..808063b4e8d 100644 --- a/modules/datadict/jsx/dataDictIndex.js +++ b/modules/datadict/jsx/dataDictIndex.js @@ -137,7 +137,7 @@ class DataDictIndex extends Component { show: true, filter: { name: 'Source From', - type: 'select', + type: 'multiselect', options: options.sourceFrom, }, }, @@ -145,9 +145,8 @@ class DataDictIndex extends Component { label: 'Name', show: true, filter: { - name: 'Source From', - type: 'multiselect', - options: options.sourceFrom, + name: 'Name', + type: 'text', }, }, { From d4d2c57c544ad144c0906925a67462b1f00fd9bb Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 17 Mar 2021 10:37:02 -0400 Subject: [PATCH 43/60] Change Created response code to No Content. The value is being updated, not necessarily created. --- modules/dictionary/php/fields.class.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dictionary/php/fields.class.inc b/modules/dictionary/php/fields.class.inc index 82b3d4d5da1..0e3d23479bc 100644 --- a/modules/dictionary/php/fields.class.inc +++ b/modules/dictionary/php/fields.class.inc @@ -60,7 +60,7 @@ class Fields extends \NDB_Page return (new \LORIS\Http\Response()) ->withHeader("Content-Type", "text/plain") ->withHeader("X-StatusDesc", $status) - ->withStatus(201) + ->withStatus(204) ->withBody( new \LORIS\Http\StringStream("") ); From 7e98577a31193c68debaac81dbb4e88c06909aae Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 17 Mar 2021 10:38:23 -0400 Subject: [PATCH 44/60] Fix typo Co-authored-by: Xavier Lecours --- modules/dictionary/php/moduledict.class.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dictionary/php/moduledict.class.inc b/modules/dictionary/php/moduledict.class.inc index 0a20a8cc420..3451d2bd5f3 100644 --- a/modules/dictionary/php/moduledict.class.inc +++ b/modules/dictionary/php/moduledict.class.inc @@ -13,7 +13,7 @@ class ModuleDict extends \NDB_Page protected $module; /** - * Override the hndle to return a dictionary for a given module + * Override the handle to return a dictionary for a given module * * @param ServerRequestInterface $request the incoming PSR7 request * From 94d91fe4261de8d0bf91bc8e8ddf3654322975a0 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 17 Mar 2021 10:40:43 -0400 Subject: [PATCH 45/60] More duplication from bad rebasing --- modules/imaging_browser/php/module.class.inc | 39 -------------------- 1 file changed, 39 deletions(-) diff --git a/modules/imaging_browser/php/module.class.inc b/modules/imaging_browser/php/module.class.inc index be4b32f1543..64df2b2c8b3 100644 --- a/modules/imaging_browser/php/module.class.inc +++ b/modules/imaging_browser/php/module.class.inc @@ -126,45 +126,6 @@ class Module extends \Module return []; } - /** - * {@inheritDoc} - * - * @param \LORIS\LorisInstance $loris The Loris instance from which the - * data dictionary for this module - * should be retrieved. - * - * @return \LORIS\Data\Dictionary\Category[] - */ - public function getDataDictionary(\LORIS\LorisInstance $loris) : iterable - { - $scope = new Scope(Scope::SESSION); - $images = new \LORIS\Data\Dictionary\Category( - "Images", - "Image Acquisitions", - ); - $items = []; - - $scantypes = \Utility::getScanTypeList(); - foreach ($scantypes as $ScanType) { - $items[] = new DictionaryItem( - $ScanType, - "$ScanType acquisition location", - $scope, - new \LORIS\Data\Types\URI(), - new Cardinality(Cardinality::MANY), - ); - // TODO: Investigate adding a file scope instead of having this apply - // on a session scope with a Many cardinality. - $items[] = new DictionaryItem( - $ScanType . "_QCStatus", - "Quality Control status for $ScanType acquisition", - $scope, - new \LORIS\Data\Types\Enumeration("Pass", "Fail"), - new Cardinality(Cardinality::MANY), - ); - } - $images = $images->withItems($items); - /** * {@inheritDoc} * From 208fec28bb52bafd21401acf8cf426f01854e98c Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 17 Mar 2021 10:48:41 -0400 Subject: [PATCH 46/60] phpcs --- modules/dictionary/php/module.class.inc | 12 +++++++++--- php/libraries/NDB_BVL_Instrument_LINST.class.inc | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/dictionary/php/module.class.inc b/modules/dictionary/php/module.class.inc index 52f4e2e4224..b56257216e6 100644 --- a/modules/dictionary/php/module.class.inc +++ b/modules/dictionary/php/module.class.inc @@ -120,10 +120,16 @@ class Module extends \Module /** * Overload loadPage to delegate 'module' to 'moduledict' in order to - * handle the URL `dictionary/module/$name`, since the Module class name - * is already used for the module descriptor. + * handle the URL `dictionary/module/$name`. LORIS delegates to classes + * of the same name as the page being accessed, bt the "Module" class + * name is already used by the Module class descriptor and can't be used + * by the page, so we arbitrarily map "module" URLs to the "moduledict" + * class name instead. + * + * @param string $page the Page name being accessed. */ - public function loadPage(string $page) { + public function loadPage(string $page) + { if ($page === 'module') { return $this->loadPage('moduledict'); } diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 0b9e6bea785..82869d1bd90 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -1238,7 +1238,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument return $this->subtests; } - /* + /** * Returns the dictionary for this instrument. * * @return \LORIS\Data\Dictionary\DictionaryItem[] From 8453e3ef9fa710ab5801c34682ce2906df7bb9bf Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 17 Mar 2021 11:02:57 -0400 Subject: [PATCH 47/60] More phpcs --- modules/dictionary/php/module.class.inc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/dictionary/php/module.class.inc b/modules/dictionary/php/module.class.inc index b56257216e6..e1f832a7da7 100644 --- a/modules/dictionary/php/module.class.inc +++ b/modules/dictionary/php/module.class.inc @@ -127,6 +127,8 @@ class Module extends \Module * class name instead. * * @param string $page the Page name being accessed. + * + * @return \NDB_Page */ public function loadPage(string $page) { From 1a2a3924d1b794a6d29e9483647f34966bb190b1 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 17 Mar 2021 13:43:04 -0400 Subject: [PATCH 48/60] Remove duplicate imports --- php/libraries/NDB_BVL_Instrument_LINST.class.inc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 82869d1bd90..2ab3870c14b 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -20,11 +20,6 @@ use \LORIS\Data\Types\IntegerType; use \LORIS\Data\Types\Enumeration; use \LORIS\Data\Types\DateType; -use \LORIS\Data\Types\StringType; -use \LORIS\Data\Types\IntegerType; -use \LORIS\Data\Types\Enumeration; -use \LORIS\Data\Types\DateType; - /** * Base class for all NeuroDB behavioural instruments * From 4c749c72ef92eae3366e0d63bfc7d306f0cf4d42 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Fri, 19 Mar 2021 11:40:29 -0400 Subject: [PATCH 49/60] Remove duplicate function --- php/libraries/NDB_BVL_Instrument_LINST.class.inc | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 2ab3870c14b..d8a50eda665 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -1232,14 +1232,4 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument { return $this->subtests; } - - /** - * Returns the dictionary for this instrument. - * - * @return \LORIS\Data\Dictionary\DictionaryItem[] - */ - public function getDataDictionary() : iterable - { - return $this->dictionary; - } } From 445d75ce53f20e5548e165cbc420546331555edc Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Fri, 19 Mar 2021 11:55:40 -0400 Subject: [PATCH 50/60] More phan and restore linst from main branch --- modules/dictionary/php/datadictrow.class.inc | 10 +++++++-- .../php/datadictrowprovisioner.class.inc | 2 ++ .../NDB_BVL_Instrument_LINST.class.inc | 22 +------------------ 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/modules/dictionary/php/datadictrow.class.inc b/modules/dictionary/php/datadictrow.class.inc index a4ab0dc3ebf..a6c59c7c17a 100644 --- a/modules/dictionary/php/datadictrow.class.inc +++ b/modules/dictionary/php/datadictrow.class.inc @@ -13,6 +13,12 @@ use \LORIS\Data\Dictionary\DictionaryItem; class DataDictRow implements \LORIS\Data\DataInstance, \LORIS\StudyEntities\AccessibleResource { + protected \Module $itemmodule; + protected Category $itemcategory; + protected DictionaryItem$item; + protected string $desc; + protected string $status; + /** * Construct a DataDictRow * @@ -25,8 +31,8 @@ class DataDictRow implements \LORIS\Data\DataInstance, */ public function __construct( \Module $itemmodule, - \LORIS\Data\Dictionary\Category $cat, - \LORIS\Data\Dictionary\DictionaryItem $item, + Category $cat, + DictionaryItem $item, string $desc, string $descstatus ) { diff --git a/modules/dictionary/php/datadictrowprovisioner.class.inc b/modules/dictionary/php/datadictrowprovisioner.class.inc index add15259afa..b2fc318aa30 100644 --- a/modules/dictionary/php/datadictrowprovisioner.class.inc +++ b/modules/dictionary/php/datadictrowprovisioner.class.inc @@ -11,6 +11,8 @@ use \LORIS\Data\Dictionary\Category; */ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance { + protected \LORIS\LorisInstance $loris; + /** * Create a DataDictRowProvisioner, which gets rows for * the datadict menu table. diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index d8a50eda665..7ad34882198 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -464,6 +464,7 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument function loadInstrumentFile(string $filename, bool $base64 = false): void { $scope = new Scope(Scope::SESSION); + if (file_exists($filename) || $base64 === true) { $this->InstrumentType = 'LINST'; @@ -652,13 +653,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument } else { $this->addTextElement($pieces[1], $pieces[2]); } - $this->dictionary[] = new DictionaryItem( - $pieces[1], - $pieces[2], - $scope, - new StringType(255), - new Cardinality(Cardinality::SINGLE), - ); } $this->dictionary[] = new DictionaryItem( $pieces[1], @@ -684,13 +678,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument } else { $this->addTextAreaElement($pieces[1], $pieces[2]); } - $this->dictionary[] = new DictionaryItem( - $pieces[1], - $pieces[2], - $scope, - new StringType(), - new Cardinality(Cardinality::SINGLE), - ); } $this->dictionary[] = new DictionaryItem( $pieces[1], @@ -719,13 +706,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument 'emptyOptionValue' => null, ]; } - $this->dictionary[] = new DictionaryItem( - $pieces[1], - $pieces[2], - $scope, - new DateType(), - new Cardinality(Cardinality::SINGLE), - ); $this->dictionary[] = new DictionaryItem( $pieces[1], From d91f7e436e7a6465e423320be51436e8cd1ba27e Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Fri, 19 Mar 2021 14:10:54 -0400 Subject: [PATCH 51/60] more missing props --- modules/dictionary/php/datadictrowprovisioner.class.inc | 1 + modules/dictionary/php/dictionary.class.inc | 2 ++ 2 files changed, 3 insertions(+) diff --git a/modules/dictionary/php/datadictrowprovisioner.class.inc b/modules/dictionary/php/datadictrowprovisioner.class.inc index b2fc318aa30..4f9ce41c40a 100644 --- a/modules/dictionary/php/datadictrowprovisioner.class.inc +++ b/modules/dictionary/php/datadictrowprovisioner.class.inc @@ -12,6 +12,7 @@ use \LORIS\Data\Dictionary\Category; class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance { protected \LORIS\LorisInstance $loris; + protected array $items; /** * Create a DataDictRowProvisioner, which gets rows for diff --git a/modules/dictionary/php/dictionary.class.inc b/modules/dictionary/php/dictionary.class.inc index 7a1bec8d2e1..09cd69b4971 100644 --- a/modules/dictionary/php/dictionary.class.inc +++ b/modules/dictionary/php/dictionary.class.inc @@ -13,6 +13,8 @@ use \Psr\Http\Message\ServerRequestInterface; class Dictionary extends \DataFrameworkMenu { + protected array $modules; + /** * {@inheritDoc} * From 0f917781f67e808e20632831a3f26f808abd4a24 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Fri, 19 Mar 2021 11:55:40 -0400 Subject: [PATCH 52/60] More phan and restore linst from main branch --- modules/dictionary/php/datadictrow.class.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dictionary/php/datadictrow.class.inc b/modules/dictionary/php/datadictrow.class.inc index a6c59c7c17a..eeab2408bca 100644 --- a/modules/dictionary/php/datadictrow.class.inc +++ b/modules/dictionary/php/datadictrow.class.inc @@ -15,7 +15,7 @@ class DataDictRow implements \LORIS\Data\DataInstance, { protected \Module $itemmodule; protected Category $itemcategory; - protected DictionaryItem$item; + protected DictionaryItem $item; protected string $desc; protected string $status; From 0bccd5d94d628c4da2ec4d4bb040e7da97beb59b Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Fri, 19 Mar 2021 14:37:44 -0400 Subject: [PATCH 53/60] Fix remaining phan problems --- modules/dictionary/php/categories.class.inc | 5 +++++ modules/dictionary/php/dictionary.class.inc | 6 ++++- modules/dictionary/php/fields.class.inc | 2 +- modules/dictionary/php/moduledict.class.inc | 2 ++ modules/dictionary/test/datadictTest.php | 25 +++++++++++---------- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/modules/dictionary/php/categories.class.inc b/modules/dictionary/php/categories.class.inc index 14a448acfa0..1ab7f0f5740 100644 --- a/modules/dictionary/php/categories.class.inc +++ b/modules/dictionary/php/categories.class.inc @@ -27,6 +27,11 @@ class Categories extends \NDB_Page // getUserModuleCategories enforces permissions and strips out modules // that don't have any dictionary. + // + // We need to ensure that $this->Module is a Module descriptor for this + // type of module, not a generic \Module in order for static analysis tools + // to realize the function is defined. + assert($this->Module instanceof Module); $modulesandcats = $this->Module->getUserModuleCategories( $user, $request->getAttribute("loris"), diff --git a/modules/dictionary/php/dictionary.class.inc b/modules/dictionary/php/dictionary.class.inc index 09cd69b4971..9f9223c4fa7 100644 --- a/modules/dictionary/php/dictionary.class.inc +++ b/modules/dictionary/php/dictionary.class.inc @@ -14,6 +14,8 @@ use \Psr\Http\Message\ServerRequestInterface; class Dictionary extends \DataFrameworkMenu { protected array $modules; + protected array $categories; + protected array $categoryitems; /** * {@inheritDoc} @@ -134,7 +136,9 @@ class Dictionary extends \DataFrameworkMenu \User $user, ServerRequestInterface $request ) : void { $this->lorisinstance = $request->getAttribute("loris"); - $modulesandcats = $this->Module->getUserModuleCategories( + + assert($this->Module instanceof Module); + $modulesandcats = $this->Module->getUserModuleCategories( $user, $this->lorisinstance, ); diff --git a/modules/dictionary/php/fields.class.inc b/modules/dictionary/php/fields.class.inc index 0e3d23479bc..4ddd329f763 100644 --- a/modules/dictionary/php/fields.class.inc +++ b/modules/dictionary/php/fields.class.inc @@ -58,9 +58,9 @@ class Fields extends \NDB_Page $request->getBody()->__toString() ); return (new \LORIS\Http\Response()) + ->withStatus(204) ->withHeader("Content-Type", "text/plain") ->withHeader("X-StatusDesc", $status) - ->withStatus(204) ->withBody( new \LORIS\Http\StringStream("") ); diff --git a/modules/dictionary/php/moduledict.class.inc b/modules/dictionary/php/moduledict.class.inc index 3451d2bd5f3..914ee1035c6 100644 --- a/modules/dictionary/php/moduledict.class.inc +++ b/modules/dictionary/php/moduledict.class.inc @@ -72,6 +72,8 @@ class ModuleDict extends \NDB_Page return new \LORIS\Http\Response\JSON\NotFound('module not found'); } + assert($this->Module instanceof Module); + $data = $this->Module->getUserModuleCategories($user, $loris, $modulename); $prov = (new DataDictRowProvisioner($loris, $data['CategoryItems'])) ->filter(new \LORIS\Data\Filters\AccessibleResourceFilter()); diff --git a/modules/dictionary/test/datadictTest.php b/modules/dictionary/test/datadictTest.php index 1799768cd0f..82af93aeff1 100644 --- a/modules/dictionary/test/datadictTest.php +++ b/modules/dictionary/test/datadictTest.php @@ -47,7 +47,7 @@ class DictionaryTestIntegrationTest extends LorisIntegrationTest * * @return void */ - function setUp() + function setUp() : void { parent::setUp(); $this->DB->insert( @@ -69,7 +69,7 @@ function setUp() * * @return void */ - function tearDown() + function tearDown() : void { parent::tearDown(); $this->DB->delete( @@ -87,17 +87,18 @@ function testDictionaryDoespageLoad() { $this->webDriver->get($this->url . "/dictionary/"); - $this->webDriver->wait(120, 1000)->until( - WebDriverExpectedCondition::presenceOfElementLocated( - WebDriverBy::Name("Name") - ) - ); + $this->webDriver->wait(120, 1000)->until( + WebDriverExpectedCondition::presenceOfElementLocated( + WebDriverBy::Name("Name") + ) + ); - $bodyText = $this->webDriver->findElement( - WebDriverBy::cssSelector("body") - )->getText(); - $this->assertContains("Data Dictionary", $bodyText); + $bodyText = $this->webDriver->findElement( + WebDriverBy::cssSelector("body") + )->getText(); + $this->assertStringContainsString("Data Dictionary", $bodyText); } + /** * Testing UI elements when page loads * @@ -110,7 +111,7 @@ function testPageUIs() $text = $this->safeFindElement( WebDriverBy::cssSelector($value) )->getText(); - $this->assertContains($key, $text); + $this->assertStringContainsString($key, $text); } } } From 9981e05ee83b7c95cbffa450b4434da4d9037eb5 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Thu, 25 Mar 2021 10:07:45 -0400 Subject: [PATCH 54/60] fix phan after rebase --- modules/dictionary/php/categories.class.inc | 3 +-- modules/dictionary/php/datadictrowprovisioner.class.inc | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/dictionary/php/categories.class.inc b/modules/dictionary/php/categories.class.inc index 1ab7f0f5740..1212443c681 100644 --- a/modules/dictionary/php/categories.class.inc +++ b/modules/dictionary/php/categories.class.inc @@ -22,8 +22,7 @@ class Categories extends \NDB_Page */ public function handle(ServerRequestInterface $request) : ResponseInterface { - $queryparams = $request->getQueryParams(); - $user = $request->getAttribute("user"); + $user = $request->getAttribute("user"); // getUserModuleCategories enforces permissions and strips out modules // that don't have any dictionary. diff --git a/modules/dictionary/php/datadictrowprovisioner.class.inc b/modules/dictionary/php/datadictrowprovisioner.class.inc index 4f9ce41c40a..70b6ac8430a 100644 --- a/modules/dictionary/php/datadictrowprovisioner.class.inc +++ b/modules/dictionary/php/datadictrowprovisioner.class.inc @@ -54,7 +54,6 @@ class DataDictRowProvisioner extends \LORIS\Data\ProvisionerInstance foreach ($cat->getItems() as $item) { $name = $item->getName(); - $desc = ''; $status = 'Unchanged'; if (isset($overrides[$name])) { $desc = $overrides[$name]['Description']; From 97bd572d205a8af07fba032e3b3fc3b16365c549 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Thu, 25 Mar 2021 10:20:29 -0400 Subject: [PATCH 55/60] Send appropriate request when not a GET --- modules/dictionary/php/moduledict.class.inc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/dictionary/php/moduledict.class.inc b/modules/dictionary/php/moduledict.class.inc index 914ee1035c6..5da05399eef 100644 --- a/modules/dictionary/php/moduledict.class.inc +++ b/modules/dictionary/php/moduledict.class.inc @@ -21,6 +21,10 @@ class ModuleDict extends \NDB_Page */ public function handle(ServerRequestInterface $request) : ResponseInterface { + if ($request->getMethod() !== 'GET') { + return new \LORIS\Http\Response\JSON\MethodNotAllowed(['GET']); + } + $user = $request->getAttribute("user"); if ($user === null && !($user instanceof \User)) { return new \LORIS\Http\Response\JSON\InternalServerError( From 16f389b9e936615191eeee316b4d528e71e577f3 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 31 Mar 2021 09:32:20 -0400 Subject: [PATCH 56/60] Spelling mistakes in comments --- modules/dictionary/jsx/dataDictIndex.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/dictionary/jsx/dataDictIndex.js b/modules/dictionary/jsx/dataDictIndex.js index f69efbefa23..38636d7710d 100644 --- a/modules/dictionary/jsx/dataDictIndex.js +++ b/modules/dictionary/jsx/dataDictIndex.js @@ -85,7 +85,7 @@ class DataDictIndex extends Component { + '/dictionary/fields/' + encodeURI(row['Field Name']); - // The fetch happens asyncronously, which means that the + // The fetch happens asynchronously, which means that the // swal closes before it returns. We find the index that // was being updated and aggressively update it, then // re-update or reset it when the PUT request returns. @@ -148,7 +148,7 @@ class DataDictIndex extends Component { } /** - * Retrive data from the provided URL and save it in state + * Retrieve data from the provided URL and save it in state * * @return {object} */ @@ -172,7 +172,7 @@ class DataDictIndex extends Component { * @param {array} rowData - array of cell contents for a specific row * @param {array} rowHeaders - array of table headers (column names) * - * @return {*} a formated table cell for a given column + * @return {*} a formatted table cell for a given column */ formatColumn(column, cell, rowData, rowHeaders) { const hasEditPermission = loris.userHasPermission('data_dict_edit'); From 58cb857ae7ecdb90696f127e4ecb3fdf12316ff1 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 31 Mar 2021 09:48:19 -0400 Subject: [PATCH 57/60] Check for resp.ok --- modules/dictionary/jsx/dataDictIndex.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/dictionary/jsx/dataDictIndex.js b/modules/dictionary/jsx/dataDictIndex.js index 38636d7710d..f9975561213 100644 --- a/modules/dictionary/jsx/dataDictIndex.js +++ b/modules/dictionary/jsx/dataDictIndex.js @@ -154,7 +154,12 @@ class DataDictIndex extends Component { */ fetchData() { return fetch(this.props.dataURL, {credentials: 'same-origin'}) - .then((resp) => resp.json()) + .then((resp) => { + if (!resp.ok) { + throw new Error('invalid response'); + } + return resp.json(); + }) .then((data) => { this.setState({data}); }) From 312c1ed550e5c1b1241cb63cc501f69347be2561 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 31 Mar 2021 11:16:50 -0400 Subject: [PATCH 58/60] Add SQL patch --- SQL/0000-00-01-Modules.sql | 1 + SQL/New_patches/2021-03-31-NewDictionaryModule.sql | 1 + 2 files changed, 2 insertions(+) create mode 100644 SQL/New_patches/2021-03-31-NewDictionaryModule.sql diff --git a/SQL/0000-00-01-Modules.sql b/SQL/0000-00-01-Modules.sql index 1684554ea68..e83902cdd2f 100644 --- a/SQL/0000-00-01-Modules.sql +++ b/SQL/0000-00-01-Modules.sql @@ -23,6 +23,7 @@ INSERT INTO modules (Name, Active) VALUES ('data_release', 'Y'); INSERT INTO modules (Name, Active) VALUES ('datadict', 'Y'); INSERT INTO modules (Name, Active) VALUES ('dataquery', 'Y'); INSERT INTO modules (Name, Active) VALUES ('dicom_archive', 'Y'); +INSERT INTO modules (Name, Active) VALUES ('dictionary', 'Y'); INSERT INTO modules (Name, Active) VALUES ('document_repository', 'Y'); INSERT INTO modules (Name, Active) VALUES ('examiner', 'Y'); INSERT INTO modules (Name, Active) VALUES ('genomic_browser', 'Y'); diff --git a/SQL/New_patches/2021-03-31-NewDictionaryModule.sql b/SQL/New_patches/2021-03-31-NewDictionaryModule.sql new file mode 100644 index 00000000000..32f79abcab6 --- /dev/null +++ b/SQL/New_patches/2021-03-31-NewDictionaryModule.sql @@ -0,0 +1 @@ +INSERT INTO modules (Name, Active) VALUES ('dictionary', 'Y'); From eaf2f4e59f25103a11e5a2e3e008a4792e74eafb Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 12 Apr 2021 13:00:37 -0400 Subject: [PATCH 59/60] Fixes from rebase --- php/libraries/NDB_BVL_Instrument_LINST.class.inc | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 7ad34882198..8e268745066 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -542,13 +542,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument $this->testName = $this->testName ?? trim($pieces[1]); $this->table = $this->table ?? trim($pieces[1]); - // Now that we have the category, add things that are added by addMetaDatafields() but - // aren't in the .linst file - $this->dictcategory = new \LORIS\Data\Dictionary\Category( - $this->testName, - $this->getFullName() - ); - // Now that we have the category, add things that are added by // addMetaDatafields() but aren't in the .linst file $this->dictionary = array_merge( @@ -565,14 +558,14 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument 'Candidate_age', 'Candidate Age (Months)', $scope, - new \LORIS\Data\Types\Interval(), + new \LORIS\Data\Types\Duration(), new Cardinality(Cardinality::SINGLE), ), new DictionaryItem( 'Window_Difference', 'Window difference from test battery (days)', $scope, - new \LORIS\Data\Types\Interval(), + new \LORIS\Data\Types\Duration(), new Cardinality(Cardinality::SINGLE), ), new DictionaryItem( From 35294f07a3a865f64c9922cdd92aceaae4a2b605 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 12 Apr 2021 13:03:03 -0400 Subject: [PATCH 60/60] Restore unnecessary changes from main --- php/libraries/Database.class.inc | 2 +- php/libraries/LorisForm.class.inc | 6 +-- .../NDB_BVL_Instrument_LINST.class.inc | 37 ------------------- 3 files changed, 3 insertions(+), 42 deletions(-) diff --git a/php/libraries/Database.class.inc b/php/libraries/Database.class.inc index 10639b70447..859b0d8d8dc 100644 --- a/php/libraries/Database.class.inc +++ b/php/libraries/Database.class.inc @@ -177,7 +177,7 @@ class Database . "and the supplied password." ); } - $this->_PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->_PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); $this->_PDO->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $this->_preparedStoreHistory = $this->_PDO->prepare( diff --git a/php/libraries/LorisForm.class.inc b/php/libraries/LorisForm.class.inc index 594a2cc47e3..c7be45c73cf 100644 --- a/php/libraries/LorisForm.class.inc +++ b/php/libraries/LorisForm.class.inc @@ -148,7 +148,7 @@ class LorisForm $el['type'] = 'select'; $el['options'] = $options; - if (isset($attribs['multiple'])) { + if (isset($attribs['multiple']) || in_array('multiple', $attribs, true)) { $el['multiple'] = 'multiple'; } return $el; @@ -912,9 +912,7 @@ class LorisForm } $element['html'] = $this->renderElement($element); - if (!is_array($element['html'])) { - $retVal .= $element['html']; - } + $retVal .= $element['html']; if (isset($el['options']['postfix_wrapper'])) { $retVal .= $el['options']['postfix_wrapper']; diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 8e268745066..0cb05321408 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -541,43 +541,6 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument // the feature is completely deprecated. $this->testName = $this->testName ?? trim($pieces[1]); $this->table = $this->table ?? trim($pieces[1]); - - // Now that we have the category, add things that are added by - // addMetaDatafields() but aren't in the .linst file - $this->dictionary = array_merge( - $this->dictionary, - [ - new DictionaryItem( - 'Date_taken', - 'Date of Administration', - $scope, - new \LORIS\Data\Types\DateType(), - new Cardinality(Cardinality::SINGLE), - ), - new DictionaryItem( - 'Candidate_age', - 'Candidate Age (Months)', - $scope, - new \LORIS\Data\Types\Duration(), - new Cardinality(Cardinality::SINGLE), - ), - new DictionaryItem( - 'Window_Difference', - 'Window difference from test battery (days)', - $scope, - new \LORIS\Data\Types\Duration(), - new Cardinality(Cardinality::SINGLE), - ), - new DictionaryItem( - 'Examiner', - 'Examiner', - $scope, - // Should this be an enum of examiners? - new StringType(255), - new Cardinality(Cardinality::SINGLE), - ), - ] - ); break; case 'title': $this->fullName = $pieces[1];